001package jmri.jmrix.loconet.locoio;
002
003import java.awt.Color;
004import java.awt.event.ActionEvent;
005import java.awt.event.ActionListener;
006import java.awt.event.FocusEvent;
007import java.awt.event.FocusListener;
008import java.beans.PropertyChangeEvent;
009import javax.swing.Box;
010import javax.swing.BoxLayout;
011import javax.swing.DefaultCellEditor;
012import javax.swing.JButton;
013import javax.swing.JComboBox;
014import javax.swing.JLabel;
015import javax.swing.JOptionPane;
016import javax.swing.JPanel;
017import javax.swing.JScrollPane;
018import javax.swing.JTable;
019import javax.swing.JTextField;
020import javax.swing.border.EmptyBorder;
021import javax.swing.table.TableCellEditor;
022import javax.swing.table.TableColumnModel;
023import jmri.jmrix.loconet.LnTrafficController;
024import jmri.jmrix.loconet.LocoNetSystemConnectionMemo;
025import jmri.util.table.ButtonEditor;
026import jmri.util.table.ButtonRenderer;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030/**
031 * Panel to display and program a LocoIO configuration.
032 * Marked Legacy/Deprecated since 2017 version 4.12.
033 *
034 * @author Bob Jacobsen Copyright (C) 2002
035 * @author Egbert Broerse 2018
036 */
037public class LocoIOPanel extends jmri.jmrix.loconet.swing.LnPanel
038        implements java.beans.PropertyChangeListener {
039
040    public LocoIOPanel() {
041        super();
042
043    }
044
045    @Override
046    public void initComponents(LocoNetSystemConnectionMemo memo) {
047        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
048        ln = memo.getLnTrafficController();
049        // creating the table (done here to ensure order OK)
050        data = new LocoIOData(Integer.valueOf(addrField.getText(), 16).intValue(),
051                Integer.valueOf(subAddrField.getText(), 16).intValue(),
052                ln);
053        model = new LocoIOTableModel(data);
054        table = new JTable(model);
055        scroll = new JScrollPane(table);
056        empty = new EmptyBorder(5, 5, 5, 5);
057
058        data.addPropertyChangeListener(this);
059
060        // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
061        // table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
062        table.setShowHorizontalLines(true);
063        table.setAutoCreateColumnsFromModel(true);
064
065        TableColumnModel tcm = table.getColumnModel();
066        // install a ComboBox editor on the OnMode column
067        JComboBox<String> comboOnBox = new JComboBox<String>(data.getLocoIOModeList().getValidModes());
068        comboOnBox.setEditable(true);
069        DefaultCellEditor modeEditor = new DefaultCellEditor(comboOnBox);
070        tcm.getColumn(LocoIOTableModel.MODECOLUMN).setCellEditor(modeEditor);
071
072        // install a button renderer & editor in the Read, Write and Compare columns
073        ButtonRenderer buttonRenderer = new ButtonRenderer();
074        tcm.getColumn(LocoIOTableModel.READCOLUMN).setCellRenderer(buttonRenderer);
075        tcm.getColumn(LocoIOTableModel.WRITECOLUMN).setCellRenderer(buttonRenderer);
076        tcm.getColumn(LocoIOTableModel.CAPTURECOLUMN).setCellRenderer(buttonRenderer);
077
078        TableCellEditor buttonEditor = new ButtonEditor(new JButton());
079        tcm.getColumn(LocoIOTableModel.READCOLUMN).setCellEditor(buttonEditor);
080        tcm.getColumn(LocoIOTableModel.WRITECOLUMN).setCellEditor(buttonEditor);
081        tcm.getColumn(LocoIOTableModel.CAPTURECOLUMN).setCellEditor(buttonEditor);
082
083        // ensure the table rows, columns have enough room for buttons and comboBox contents
084        table.setRowHeight(new JButton(Bundle.getMessage("ButtonCapture")).getPreferredSize().height);
085        for (int col = 0; col < LocoIOTableModel.HIGHESTCOLUMN; col++) {
086            table.getColumnModel().getColumn(col).setPreferredWidth(model.getPreferredWidth(col));
087        }
088
089        // Top (config) row for SV0, SV1, SV2, the board sub address and the PIC version
090        JPanel p1 = new JPanel();
091        p1.setLayout(new BoxLayout(p1, BoxLayout.X_AXIS));
092        JPanel p1a = new JPanel();
093        p1a.add(new JLabel(Bundle.getMessage("LocoioAddressLabel")));
094        p1a.add(addrField);
095        addrField.setPreferredSize(spacer.getPreferredSize());
096        addrField.setToolTipText(Bundle.getMessage("AddressToolTip"));
097        p1a.add(new JLabel("/"));
098        p1a.add(subAddrField);
099        subAddrField.setPreferredSize(spacer.getPreferredSize());
100        subAddrField.setToolTipText(Bundle.getMessage("SubAddressToolTip"));
101        p1.add(p1a);
102        p1.add(new JLabel("   ")); // prevent the glue pulling on the address fields
103        p1.add(Box.createGlue());  // -------------------
104        probeButton = new JButton(Bundle.getMessage("ButtonProbe"));
105        probeButton.addActionListener(new ActionListener() {
106            @Override
107                    public void actionPerformed(ActionEvent a) {
108                        data.setLIOVersion("<Not found>"); // NOI18N
109                        LocoIO.probeLocoIOs(ln);
110                    }
111                });
112        p1.add(probeButton);
113        p1.add(Box.createGlue());  // -------------------
114        readAllButton = new JButton(Bundle.getMessage("ButtonReadAll"));
115        readAllButton.addActionListener(new ActionListener() {
116            @Override
117                    public void actionPerformed(ActionEvent a) {
118                        data.readAll();
119                    }
120                });
121        p1.add(readAllButton);
122        writeAllButton = new JButton(Bundle.getMessage("ButtonWriteAll"));
123        writeAllButton.addActionListener(new ActionListener() {
124            @Override
125                    public void actionPerformed(ActionEvent a) {
126                        data.writeAll();
127                    }
128                });
129        p1.add(writeAllButton);
130        p1.add(Box.createGlue());  // -------------------
131        addrSetButton = new JButton(Bundle.getMessage("ButtonSetAddress"));
132        p1.add(addrSetButton);
133        addrSetButton.addActionListener(new ActionListener() {
134            @Override
135                    public void actionPerformed(ActionEvent a) {
136                        addrSet();
137                    }
138                });
139        p1.add(Box.createGlue());  // -------------------
140
141        /*
142         openButton = new JButton("Load...");
143         openButton.setEnabled(false);
144         p1.add(openButton);
145
146         saveButton = new JButton("Save...");
147         saveButton.setEnabled(false);
148         p1.add(saveButton);
149         */
150        // bottom (status) row, smaller text size display in gray
151        JPanel p2 = new JPanel();
152        p2.setLayout(new BoxLayout(p2, BoxLayout.X_AXIS));
153        p2.add(revLabel);
154        p2.add(locobuffer);
155        revLabel.setFont(revLabel.getFont().deriveFont(0.9f * addrField.getFont().getSize())); // a bit smaller
156        revLabel.setForeground(Color.gray);
157        locobuffer.setFont(locobuffer.getFont().deriveFont(0.9f * addrField.getFont().getSize()));
158        locobuffer.setForeground(Color.gray);
159        p2.add(Box.createGlue());  // -------------------
160        p2.add(statusLabel);
161        p2.add(status);
162        statusLabel.setFont(statusLabel.getFont().deriveFont(0.9f * addrField.getFont().getSize()));
163        statusLabel.setForeground(Color.gray);
164        status.setFont(status.getFont().deriveFont(0.9f * addrField.getFont().getSize()));
165        status.setForeground(Color.gray);
166        p2.add(Box.createGlue());  // -------------------
167        p2.add(fwLabel);
168        p2.add(firmware);
169        fwLabel.setFont(fwLabel.getFont().deriveFont(0.9f * addrField.getFont().getSize()));
170        fwLabel.setForeground(Color.gray);
171        firmware.setFont(firmware.getFont().deriveFont(0.9f * addrField.getFont().getSize()));
172        firmware.setForeground(Color.gray);
173
174        JPanel p3 = new JPanel();
175        p3.setLayout(new BoxLayout(p3, BoxLayout.Y_AXIS));
176        p3.add(p1);
177        scroll.setBorder(empty);
178        p3.add(scroll);
179
180        add(p3);
181        add(p2);
182
183        // updating the Board address needs to be conveyed to the table
184        ActionListener al4UnitAddress = new ActionListener() {
185            @Override
186            public void actionPerformed(ActionEvent a) {
187                log.debug("address =|{}|", addrField.getText());
188                addrField.setText(addrField.getText().trim());
189                subAddrField.setText(subAddrField.getText().trim());
190                // check for empty address fields
191                if (addrField.getText() == null || addrField.getText().length() < 1) {
192                    addrField.setText("1");
193                    log.warn("empty Address, set to 1");
194                    return;
195                }
196                if (subAddrField.getText() == null || subAddrField.getText().length() < 1) {
197                    subAddrField.setText("0");
198                    log.warn("empty SubAddress, set to 0");
199                    return;
200                }
201                try {
202                    data.setUnitAddress(
203                            Integer.valueOf(addrField.getText(), 16).intValue(),
204                            Integer.valueOf(subAddrField.getText(), 16).intValue());
205                } catch (NullPointerException e) {
206                    log.error("Caught NullPointerException", e); // NOI18N
207                } catch (NumberFormatException ne) {
208                    log.error("Caught NumberFormatException", ne); // NOI18N
209                }
210            }
211        };
212        FocusListener fl4UnitAddress = new FocusListener() {
213            @Override
214            public void focusGained(FocusEvent event) {
215            }
216
217            @Override
218            public void focusLost(FocusEvent event) {
219                log.debug("address =|{}|", addrField.getText());
220                addrField.setText(addrField.getText().trim());
221                subAddrField.setText(subAddrField.getText().trim());
222                // check for empty address fields
223                if (addrField.getText().length() < 1) {
224                    addrField.setText("1");
225                    log.warn("empty LocoIO Address, set to 1");
226                    return;
227                }
228                if (subAddrField.getText().length() < 1) {
229                    subAddrField.setText("0");
230                    log.warn("empty LocoIO SubAddress, set to 0");
231                    return;
232                }
233                try {
234                    data.setUnitAddress(
235                            Integer.valueOf(addrField.getText(), 16).intValue(),
236                            Integer.valueOf(subAddrField.getText(), 16).intValue());
237                } catch (NullPointerException e) {
238                    log.error("Caught NullPointerException", e); // NOI18N
239                }
240            }
241        };
242
243        addrField.addActionListener(al4UnitAddress);
244        subAddrField.addActionListener(al4UnitAddress);
245        addrField.addFocusListener(fl4UnitAddress);
246        subAddrField.addFocusListener(fl4UnitAddress);
247
248        try {
249            data.setUnitAddress(0x51, 0x00);
250        } catch (NullPointerException e) {
251            log.error("Caught NullPointerException", e); // NOI18N
252        }
253    }
254
255    LnTrafficController ln;
256
257    @Override
258    public String getHelpTarget() {
259        return "package.jmri.jmrix.loconet.locoio.LocoIOFrame"; // NOI18N
260    }
261
262    @Override
263    public String getTitle() {
264        return getTitle(Bundle.getMessage("MenuItemLocoIOProgrammer"));
265    }
266
267    /**
268     * The SET LOCOIO ADDRESS button was pressed. Since this does a broadcast
269     * program-all to every LocoIO board on the LocoNet, it needs to be used
270     * with caution.
271     * @return Status of user warning
272     */
273    protected int cautionAddrSet() {
274        log.info("Caution: 'Set LocoIO Address' is a broadcast operation to ALL boards on this connection"); // NOI18N
275        return JOptionPane.showOptionDialog(this,
276                Bundle.getMessage("LocoIoSetAddressWarnDialog"),
277                Bundle.getMessage("WarningTitle"),
278                0, JOptionPane.INFORMATION_MESSAGE, null,
279                new Object[]{Bundle.getMessage("ButtonCancel"), Bundle.getMessage("ButtonOK")}, null);
280    }
281
282    /**
283     * Change the hardware address of all connected LocoIO boards using inputs from UI.
284     * Checks validity of entries.
285     * <p>
286     * Clips off any address value over 126 (trimmed and prechecked on entry in UI)
287     */
288    protected void addrSet() {
289        // caution user
290        int retval = cautionAddrSet();
291        if (retval != 1) {
292            return; // user cancelled
293        }
294        int fullAddress;
295        int address = Integer.valueOf(addrField.getText(), 16).intValue();
296        int subAddress = Integer.valueOf(subAddrField.getText(), 16).intValue();
297
298        if (address > 126) {
299            log.warn("Address must be [1..126], was {}", // NOI18N
300                    address);
301        }
302        if ((address & 0xFF) == 0x80) {
303            log.warn("Only a LocoBuffer may use address 0x80"); // NOI18N
304            return;
305        }
306
307        if (subAddress > 126) {
308            log.warn("subAddress must be [1..126], was {}", // NOI18N
309                    subAddress);
310        }
311        fullAddress = 0x0100 | (address & 0x07F); // range is [1..79, 81..127]
312        subAddress = subAddress & 0x07F; // range is [1..126] clipping off any higher value
313        LocoIO.programLocoIOAddress(fullAddress, subAddress, ln); // broadcast new address values
314        // need to update UI to show values used, was input validated upon entry in UI pane?
315    }
316
317    @Override
318    public void propertyChange(PropertyChangeEvent evt) {
319        // these messages can arrive without a complete
320        // GUI, in which case we just ignore them
321        if (evt.getPropertyName().equals("UnitAddress")) { // NOI18N
322            Integer i = (Integer) evt.getNewValue();
323            int v = i.intValue();
324            v = v & 0xFF;
325            if (addrField != null) {
326                addrField.setText(Integer.toHexString(v));
327            }
328            if (firmware != null) {
329                firmware.setText("<" + Bundle.getMessage("BeanStateUnknown").toLowerCase() + ">  "); // some trailing space at bottom right corner of pane
330            }
331        }
332        if (evt.getPropertyName().equals("UnitSubAddress")) { // NOI18N
333            Integer i = (Integer) evt.getNewValue();
334            int v = i.intValue();
335            if (subAddrField != null) {
336                subAddrField.setText(Integer.toHexString(v));
337            }
338            if (firmware != null) { // remove former fw info + some trailing space at bottom right corner of pane
339                firmware.setText("<" + Bundle.getMessage("BeanStateUnknown").toLowerCase() + ">  ");
340            }
341        }
342        if (evt.getPropertyName().equals("LBVersionChange")) { // NOI18N
343            String v = (String) evt.getNewValue();
344            if (locobuffer != null) {
345                locobuffer.setText(v);
346            }
347        }
348        if (evt.getPropertyName().equals("LIOVersionChange")) { // NOI18N
349            String v = (String) evt.getNewValue();
350            if (firmware != null) {
351                firmware.setText(v + "  "); // add some trailing space at bottom right corner of pane
352            }
353        }
354        if (evt.getPropertyName().equals("StatusChange")) { // NOI18N
355            String v = (String) evt.getNewValue();
356            if (status != null) {
357                status.setText(v);
358            }
359        }
360    }
361
362    JTextField addrField = new JTextField("00");
363    JTextField subAddrField = new JTextField("00");
364    final static JTextField spacer = new JTextField("123");
365    JLabel status = new JLabel(Bundle.getMessage("StateUnknown"));
366    JLabel firmware = new JLabel(Bundle.getMessage("StateUnknown"));
367    JLabel locobuffer = new JLabel(Bundle.getMessage("StateUnknown"));
368    JLabel revLabel = new JLabel(" LocoBuffer rev: "); // a bit of room on the left NOI18N
369    JLabel statusLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("StatusCol")) + " ");
370    JLabel fwLabel = new JLabel("LocoIO Firmware rev: "); // NOI18N
371
372    JButton addrSetButton = null;
373    JButton probeButton = null;
374    JButton readAllButton = null;
375    JButton writeAllButton = null;
376    JButton saveButton = null;
377    JButton openButton = null;
378
379    LocoIOData data;
380    LocoIOTableModel model;
381    JTable table;
382    JScrollPane scroll;
383    EmptyBorder empty;
384
385    @Override
386    public void dispose() {
387        // dispose of the model
388        model.dispose();
389        // take apart the JFrame
390        super.dispose();
391        model = null;
392        table = null;
393        scroll = null;
394        readAllButton = null;
395        writeAllButton = null;
396        addrField = null;
397        subAddrField = null;
398        status = null;
399        firmware = null;
400        locobuffer = null;
401        saveButton = null;
402        openButton = null;
403    }
404
405    private final static Logger log = LoggerFactory.getLogger(LocoIOPanel.class);
406
407}