001package jmri.jmrix.loconet.slotmon;
002
003import java.awt.event.ActionEvent;
004import java.text.DecimalFormat;
005import java.text.NumberFormat;
006import java.util.ArrayList;
007import java.util.List;
008
009import javax.swing.*;
010import javax.swing.table.DefaultTableCellRenderer;
011import javax.swing.table.TableCellEditor;
012import javax.swing.table.TableCellRenderer;
013import javax.swing.table.TableColumnModel;
014import javax.swing.table.TableRowSorter;
015
016import jmri.InstanceManager;
017import jmri.jmrix.loconet.LnConstants;
018import jmri.jmrix.loconet.LocoNetSlot;
019import jmri.jmrix.loconet.SlotListener;
020import jmri.jmrix.loconet.SlotMapEntry.SlotType;
021import jmri.swing.JmriJTablePersistenceManager;
022import jmri.util.swing.WrapLayout;
023import jmri.util.table.*;
024
025/**
026 * Frame providing a command station slot manager.
027 * <p>
028 * Slots 102 through 127 are normally not used for loco control, so are shown
029 * separately.
030 *
031 * @author Bob Jacobsen Copyright (C) 2001
032 */
033public class SlotMonPane extends jmri.jmrix.loconet.swing.LnPanel implements SlotListener  {
034
035    /**
036     * Controls whether not-in-use slots are shown
037     */
038    protected final JCheckBox showUnusedCheckBox = new JCheckBox();
039    /**
040     * Controls whether system slots (0, 121-127) are shown
041     */
042    protected final JCheckBox showSystemCheckBox = new JCheckBox();
043
044    private JLabel dcsCSLabel = new JLabel(Bundle.getMessage("SlotMonCSLabel"));
045    private JTextField dcsType = new JTextField();
046    private JLabel dcsSlotsLabel = new JLabel(Bundle.getMessage("SlotMonTotalSlots"));
047    private JTextField dcsSlots = new JTextField();
048
049    private final JButton estopAllButton = new JButton(Bundle.getMessage("ButtonSlotMonEStopAll"));
050
051    //Added by Jeffrey Machacek 2013
052    private final JButton clearAllButton = new JButton(Bundle.getMessage("ButtonSlotMonClearAll"));
053    private final JButton refreshAllButton = new JButton(Bundle.getMessage("ButtonSlotRefresh"));
054
055    private JPanel topPanel;  // the panel across the top that holds buttons
056
057    private SlotMonDataModel slotModel;
058    private JTable slotTable;
059    private JScrollPane slotScroll;
060    private transient TableRowSorter<SlotMonDataModel> sorter;
061
062    public SlotMonPane() {
063        super();
064    }
065
066    @Override
067    public void initComponents(jmri.jmrix.loconet.LocoNetSystemConnectionMemo memo) {
068        super.initComponents(memo);
069        int columns = 40;
070        if (memo.getSlotManager().getLoconetProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
071            columns=20;
072        }
073        slotModel = new SlotMonDataModel(memo.getSlotManager().getNumSlots(), columns, memo);
074        slotTable = new JTable(slotModel);
075        slotTable.setName(this.getTitle());
076
077        sorter = new TableRowSorter<>(slotModel);
078        slotTable.setRowSorter(sorter);
079        slotScroll = new JScrollPane(slotTable);
080
081        // configure items for GUI
082        showUnusedCheckBox.setText(Bundle.getMessage("TextSlotMonShowUnused"));
083        showUnusedCheckBox.setVisible(true);
084        showUnusedCheckBox.setSelected(false);
085        showUnusedCheckBox.setToolTipText(Bundle.getMessage("TooltipSlotMonShowUnused"));
086
087        showSystemCheckBox.setText(Bundle.getMessage("TextSlotMonShowSystem"));
088        showSystemCheckBox.setVisible(true);
089        showSystemCheckBox.setSelected(false);
090        showSystemCheckBox.setToolTipText(Bundle.getMessage("TooltipSlotMonShowSystem"));
091
092        // allow reordering of the columns
093        slotTable.getTableHeader().setReorderingAllowed(true);
094
095        // shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
096        slotTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
097
098        // resize columns as requested
099        for (int i = 0; i < slotTable.getColumnCount(); i++) {
100            int width = slotModel.getPreferredWidth(i);
101            slotTable.getColumnModel().getColumn(i).setPreferredWidth(width);
102        }
103        slotTable.sizeColumnsToFit(-1);
104
105        InstanceManager.getOptionalDefault(JmriJTablePersistenceManager.class).ifPresent((tpm) -> {
106            // unable to persist because Default class provides no mechanism to
107            // ensure window is destroyed when closed or that existing window is
108            // reused when hidden and user reopens it from menu
109            // tpm.persist(slotTable, true);
110        });
111
112        // install a button renderer & editor in the "DISP" column for freeing a slot
113        setColumnToHoldButton(slotTable, slotTable.convertColumnIndexToView(SlotMonDataModel.DISPCOLUMN));
114
115        // install a button renderer & editor in the "ESTOP" column for stopping a loco
116        setColumnToHoldEStopButton(slotTable, slotTable.convertColumnIndexToView(SlotMonDataModel.ESTOPCOLUMN));
117
118        // Install a numeric format for ConsistAddress
119        setColumnForBlankWhenZero(slotTable, slotTable.convertColumnIndexToView(SlotMonDataModel.CONSISTADDRESS));
120        // add listener object so checkboxes function
121
122        refreshAllButton.addActionListener((ActionEvent e) -> {
123            slotModel.refreshSlots();
124        });
125
126        showUnusedCheckBox.addActionListener((ActionEvent e) -> {
127            filter();
128        });
129        showSystemCheckBox.addActionListener((ActionEvent e) -> {
130            filter();
131        });
132
133        // add listener object so stop all button functions
134        estopAllButton.addActionListener((ActionEvent e) -> {
135            slotModel.estopAll();
136        });
137
138        //Jeffrey 6/29/2013
139        clearAllButton.addActionListener((ActionEvent e) -> {
140            slotModel.clearAllSlots();
141        });
142
143        // adjust model to default settings
144        filter();
145
146        // general GUI config
147        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
148
149        // install items in GUI
150        topPanel = new JPanel();
151        topPanel.setLayout(new WrapLayout());
152
153        topPanel.add(dcsCSLabel);
154        dcsType.setEditable(false);
155        topPanel.add(dcsType);
156        topPanel.add(dcsSlotsLabel);
157        dcsSlots.setEditable(false);
158        topPanel.add(dcsSlots);
159        showHideSlot250Data(false);
160        topPanel.add(refreshAllButton);
161        topPanel.add(showUnusedCheckBox);
162        topPanel.add(showSystemCheckBox);
163        topPanel.add(estopAllButton);
164        topPanel.add(clearAllButton);
165
166        add(topPanel);
167        add(slotScroll);
168
169        memo.getSlotManager().addSlotListener(this);
170
171        // set top panel size
172        if (topPanel.getMaximumSize().height > 0 && topPanel.getMaximumSize().width > 0) {
173            topPanel.setMaximumSize(topPanel.getPreferredSize());
174        }
175    }
176
177    void setColumnToHoldButton(JTable slotTable, int column) {
178        TableColumnModel tcm = slotTable.getColumnModel();
179        // install the button renderers & editors in this column
180        ButtonRenderer buttonRenderer = new ButtonRenderer();
181        tcm.getColumn(column).setCellRenderer(buttonRenderer);
182        // ensure the table rows, columns have enough room for buttons
183        TableCellEditor buttonEditor = new ButtonEditor(new JButton());
184        slotTable.setDefaultEditor(JButton.class, buttonEditor);
185        slotTable.setRowHeight(new JButton("  " + slotModel.getValueAt(1, column)).getPreferredSize().height);
186        slotTable.getColumnModel().getColumn(column)
187                .setPreferredWidth(new JButton("  " + slotModel.getValueAt(1, column)).getPreferredSize().width);
188    }
189
190    /*
191     * Helper class to format number and optionally make blank when zero
192     */
193    private static class NumberFormatRenderer extends DefaultTableCellRenderer
194    {
195        public NumberFormatRenderer(String pattern, boolean suppressZero) {
196            super();
197            this.pattern = pattern;
198            this.suppressZero = suppressZero;
199            setHorizontalAlignment(JLabel.RIGHT);
200        }
201        @Override
202        public void setValue(Object value)
203        {
204            try
205            {
206                if (value != null && value instanceof Number) {
207                    if (suppressZero && ((Number) value).doubleValue() == 0.0 ) {
208                        value = "";
209                    }
210                    NumberFormat formatter = new DecimalFormat(pattern);
211                    value = formatter.format(value);
212                }
213            }
214            catch(IllegalArgumentException e) {}
215            super.setValue(value);
216        }
217        private String pattern;
218        private boolean suppressZero;
219    }
220
221    void setColumnForBlankWhenZero(JTable slotTable, int column) {
222        TableColumnModel tcm = slotTable.getColumnModel();
223        TableCellRenderer renderer = new NumberFormatRenderer("####",true);
224        tcm.getColumn(column).setCellRenderer(renderer);
225    }
226
227    void setColumnToHoldEStopButton(JTable slotTable, int column) {
228        TableColumnModel tcm = slotTable.getColumnModel();
229        // install the button renderers & editors in this column
230        ButtonRenderer buttonRenderer = new ButtonRenderer();
231        tcm.getColumn(column).setCellRenderer(buttonRenderer);
232        // ensure the table rows, columns have enough room for buttons
233        TableCellEditor buttonEditor = new ButtonEditor(new JButton());
234        slotTable.setDefaultEditor(JButton.class, buttonEditor);
235        slotTable.setRowHeight(new JButton("  " + slotModel.getValueAt(1, column)).getPreferredSize().height);
236        slotTable.getColumnModel().getColumn(column)
237                .setPreferredWidth(new JButton("  " + slotModel.getValueAt(1, column)).getPreferredSize().width);
238    }
239
240    @Override
241    public String getHelpTarget() {
242        return "package.jmri.jmrix.loconet.slotmon.SlotMonFrame"; // NOI18N
243    }
244
245    @Override
246    public String getTitle() {
247        return getTitle(Bundle.getMessage("MenuItemSlotMonitor"));
248    }
249
250    @Override
251    public void dispose() {
252        slotModel.dispose();
253        slotModel = null;
254        slotTable = null;
255        slotScroll = null;
256        super.dispose();
257    }
258
259    private void filter() {
260        RowFilter<SlotMonDataModel, Integer> rf = new RowFilter<SlotMonDataModel, Integer>() {
261            @Override
262            public boolean include(RowFilter.Entry<? extends SlotMonDataModel, ? extends Integer> entry) {
263                // default filter is IN-USE and regular systems slot
264                // the default is whatever the person last closed it with
265                jmri.jmrix.loconet.LocoNetSlot slot =  entry.getModel().getSlot(entry.getIdentifier());
266                boolean include = entry.getModel().getSlot(entry.getIdentifier()).slotStatus() != LnConstants.LOCO_FREE
267                        && slot.getSlotType() == SlotType.LOCO;
268                if (slot.getSlotType() == SlotType.UNKNOWN) {
269                    return false;        // dont ever show unknown
270                }
271                if (!include && showUnusedCheckBox.isSelected() && !slot.isSystemSlot()) {
272                    include = true;
273                }
274                if (!include && showSystemCheckBox.isSelected() && slot.isSystemSlot()) {
275                    include = true;
276                }
277                return include;
278            }
279        };
280        sorter.setRowFilter(rf);
281    }
282
283    @Override
284    public List<JMenu> getMenus() {
285        List<JMenu> menuList = new ArrayList<>();
286        menuList.add(getFileMenu());
287        return menuList;
288    }
289
290    private JMenu getFileMenu(){
291        JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile")); // NOI18N
292        fileMenu.add(new JTableToCsvAction((Bundle.getMessage("ExportCsvAll")),
293            null, slotModel, "Slot_Monitor_All.csv", new int[]{
294            SlotMonDataModel.ESTOPCOLUMN})); // NOI18N
295        fileMenu.add(new JTableToCsvAction((Bundle.getMessage("ExportCsvView")),
296            slotTable, slotModel, "Slot_Monitor_View.csv", new int[]{
297            SlotMonDataModel.ESTOPCOLUMN})); // NOI18N
298        return fileMenu;
299    }
300
301    // methods to communicate with SlotManager
302    @Override
303    public synchronized void notifyChangedSlot(LocoNetSlot s) {
304        // update model from this slot
305        if (s.getSlot() == 250) {
306            if (memo.getSlotManager().getSlot250CSSlots() > 0) {
307                showHideSlot250Data(true);
308                dcsSlots.setText(Integer.toString(memo.getSlotManager().getSlot250CSSlots()));
309                dcsType.setText(memo.getSlotManager().getSlot248CommandStationType());
310
311                // set scroll size
312                if (topPanel.getMaximumSize().height > 0 && topPanel.getMaximumSize().width > 0) {
313                    topPanel.setMaximumSize(topPanel.getPreferredSize());
314                }
315                topPanel.revalidate();
316
317            }
318        }
319    }
320
321    void showHideSlot250Data(boolean b) {
322        dcsCSLabel.setVisible(b);
323        dcsSlots.setVisible(b);
324        dcsSlotsLabel.setVisible(b);
325        dcsType.setVisible(b);
326        // set scroll size
327        if (topPanel.getMaximumSize().height > 0 && topPanel.getMaximumSize().width > 0) {
328            topPanel.setMaximumSize(topPanel.getPreferredSize());
329        }
330        topPanel.revalidate();
331    }
332
333
334}