001package jmri.jmrix.grapevine.nodetable;
002
003import java.awt.Color;
004import java.awt.Dimension;
005import java.awt.event.ActionEvent;
006import java.awt.event.ActionListener;
007import javax.swing.Box;
008import javax.swing.BoxLayout;
009import javax.swing.JButton;
010import javax.swing.JLabel;
011import javax.swing.JPanel;
012import javax.swing.JScrollPane;
013import javax.swing.JTable;
014import javax.swing.SortOrder;
015import javax.swing.table.AbstractTableModel;
016import javax.swing.table.TableCellEditor;
017import javax.swing.table.TableRowSorter;
018import jmri.jmrix.grapevine.SerialMessage;
019import jmri.jmrix.grapevine.SerialReply;
020import jmri.jmrix.grapevine.nodeconfig.NodeConfigFrame;
021import jmri.jmrix.grapevine.GrapevineSystemConnectionMemo;
022import jmri.swing.RowSorterUtil;
023import jmri.util.table.ButtonEditor;
024import jmri.util.table.ButtonRenderer;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028/**
029 * Pane for user management of serial nodes. Contains a table that does the real
030 * work.
031 * <p>
032 * Nodes can be in three states:
033 * <ol>
034 *   <li>Configured
035 *   <li>Present, not configured
036 *   <li>Absent (not present)
037 * </ol>
038 *
039 * @author Bob Jacobsen Copyright (C) 2004, 2007, 2008
040 * @author Dave Duchamp Copyright (C) 2004, 2006
041 */
042public class NodeTablePane extends javax.swing.JPanel implements jmri.jmrix.grapevine.SerialListener {
043
044    private GrapevineSystemConnectionMemo memo = null;
045
046    /**
047     * Constructor method.
048     * @param _memo system connection.
049     */
050    public NodeTablePane(GrapevineSystemConnectionMemo _memo) {
051        super();
052        memo = _memo;
053    }
054
055    NodesModel nodesModel = null;
056    JLabel status;
057
058    /**
059     * Initialize the NodeTable window.
060     */
061    public void initComponents() {
062
063        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
064
065        nodesModel = new NodesModel();
066
067        JTable nodesTable = new JTable(nodesModel);
068
069        // install a button renderer & editor
070        ButtonRenderer buttonRenderer = new ButtonRenderer();
071        nodesTable.setDefaultRenderer(JButton.class, buttonRenderer);
072        TableCellEditor buttonEditor = new ButtonEditor(new JButton());
073        nodesTable.setDefaultEditor(JButton.class, buttonEditor);
074
075        TableRowSorter<NodesModel> sorter = new TableRowSorter<>(nodesModel);
076        RowSorterUtil.setSortOrder(sorter, NodesModel.STATUSCOL, SortOrder.DESCENDING);
077        nodesTable.setRowSorter(sorter);
078        nodesTable.setRowSelectionAllowed(false);
079        // ensure the table rows, columns have enough room for buttons
080        JButton spacer = new JButton("spacer");
081        nodesTable.setRowHeight(spacer.getPreferredSize().height - 4); // a bit more compact
082        nodesTable.setPreferredScrollableViewportSize(new java.awt.Dimension(580, 80));
083
084        JScrollPane scrollPane = new JScrollPane(nodesTable);
085        add(scrollPane);
086
087        // status info on bottom
088        JPanel p = new JPanel() {
089            @Override
090            public Dimension getMaximumSize() {
091                int height = getPreferredSize().height;
092                int width = super.getMaximumSize().width;
093                return new Dimension(width, height);
094            }
095        };
096        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
097        JButton b = new JButton(Bundle.getMessage("ButtonCheck"));
098        b.setToolTipText(Bundle.getMessage("TipCheck"));
099        b.addActionListener(new ActionListener() {
100            @Override
101            public void actionPerformed(ActionEvent event) {
102                startPoll();
103            }
104        });
105        p.add(b);
106        p.add(new JLabel("   ")); // spacer
107        status = new JLabel("");
108        status.setFont(status.getFont().deriveFont(0.9f * b.getFont().getSize())); // a bit smaller
109        status.setForeground(Color.gray);
110        p.add(status);
111
112        p.add(Box.createHorizontalGlue());
113
114        // Renumber button
115        b = new JButton(Bundle.getMessage("ButtonRenumber"));
116        b.addActionListener(new ActionListener() {
117            @Override
118            public void actionPerformed(ActionEvent event) {
119                renumber();
120            }
121        });
122        p.add(b);
123
124        add(p);
125        // start the search for nodes
126        startPoll();
127    }
128
129    /**
130     * Respond to Renumber button click by opening a new
131     * {@link RenumberFrame}.
132     */
133    void renumber() {
134        RenumberFrame f = new RenumberFrame(memo);
135        f.initComponents();
136        f.setVisible(true);
137    }
138
139    javax.swing.Timer timer;
140
141    /**
142     * Start the check of the actual hardware.
143     * Any check already running is stopped first.
144     */
145    public void startPoll() {
146        stopPoll();
147
148        // mark as none seen
149        for (int i = 0; i < 128; i++) {
150            scanSeen[i] = false;
151        }
152
153        status.setText(Bundle.getMessage("StatusStart"));
154
155        // create a timer to send messages
156        timer = new javax.swing.Timer(50, new java.awt.event.ActionListener() {
157            int node = 1;
158
159            @Override
160            public void actionPerformed(java.awt.event.ActionEvent e) {
161                // send message to node
162                log.debug("polling node {}", node);
163                status.setText(Bundle.getMessage("StatusRunningX", node, 127));
164                memo.getTrafficController().sendSerialMessage(SerialMessage.getPoll(node), null);
165                // done?
166                node++;
167                if (node >= 128) {
168                    // yes, stop
169                    stopPoll();
170                    // if nothing seen yet, this is a failure
171                    if (status.getText().equals(Bundle.getMessage("StatusStart"))) {
172                        status.setText(Bundle.getMessage("StatusFail"));
173                    } else {
174                        status.setText(Bundle.getMessage("StatusOK"));
175                    }
176                }
177            }
178        });
179        timer.setInitialDelay(50);
180        timer.setRepeats(true);
181        timer.start();
182
183        // redisplay the table
184        nodesModel.fireTableDataChanged();
185    }
186
187    /**
188     * Stop the check of the actual hardware, if one is running.
189     */
190    void stopPoll() {
191        if (timer != null) {
192            timer.stop();
193            timer = null;
194        }
195    }
196
197    /**
198     * Release resources when the pane is no longer needed. Stops any check of
199     * the hardware still running, so no more messages are sent once the
200     * containing window has closed.
201     */
202    public void dispose() {
203        stopPoll();
204    }
205
206    // indicate whether node has been seen
207    boolean scanSeen[] = new boolean[128];
208
209    /**
210     * Ignore messages being sent.
211     */
212    @Override
213    public void message(SerialMessage m) {
214    }
215
216    /**
217     * Listen for software version messages to know a node is present.
218     */
219    @Override
220    public void reply(SerialReply m) {
221        // set the status as having seen something
222        if (status.getText().equals(Bundle.getMessage("StatusStart"))) {
223            status.setText(Bundle.getMessage("StatusRunning"));
224        }
225        // is this a software version reply? if not, stop
226        if (m.getNumDataElements() != 2) {
227            return;
228        }
229        // does it have a real value?
230        // (getting 0x77 is just the original request)
231        if ((m.getElement(1) & 0xFF) == 0x77) {
232            return;
233        }
234        // yes, extract node number
235        int num = m.getElement(0) & 0x7F;
236        // mark as seen
237        scanSeen[num] = true;
238        // force redisplay of that line
239        nodesModel.fireTableRowsUpdated(num - 1, num - 1);
240    }
241
242    /**
243     * Set up table for selecting and showing nodes.
244     * <ol>
245     *   <li>Address
246     *   <li>Present Y/N
247     *   <li>Add/Edit button
248     * </ol>
249     */
250    public class NodesModel extends AbstractTableModel {
251        private static final int ADDRCOL = 0;
252        private static final int STATUSCOL = 1;
253        private static final int EDITCOL = 2;
254        private static final int INITCOL = 3;
255
256        private static final int LAST = 3;
257
258        @Override
259        public int getColumnCount() {
260            return LAST + 1;
261        }
262
263        @Override
264        public int getRowCount() {
265            return 127;
266        }
267
268        @Override
269        public String getColumnName(int c) {
270            switch (c) {
271                case ADDRCOL:
272                    return Bundle.getMessage("TitleAddress");
273                case STATUSCOL:
274                    return Bundle.getMessage("TitleStatus");
275                case EDITCOL:
276                    return ""; // no title over Add/Edit column
277                default:
278                    return "";
279            }
280        }
281
282        @Override
283        public Class<?> getColumnClass(int c) {
284            if (c == EDITCOL || c == INITCOL) {
285                return JButton.class;
286            } else if (c == ADDRCOL) {
287                return Integer.class;
288            } else {
289                return String.class;
290            }
291        }
292
293        @Override
294        public boolean isCellEditable(int r, int c) {
295            return (c == EDITCOL || c == INITCOL);
296        }
297
298        @Override
299        public Object getValueAt(int r, int c) {
300            // r is row number, from 0, and therefore r+1 is node number
301            switch (c) {
302                case ADDRCOL:
303                    return Integer.valueOf(r + 1);
304                case STATUSCOL:
305                    // see if node exists
306                    if (memo.getTrafficController().getNodeFromAddress(r + 1) != null) {
307                        return Bundle.getMessage("StatusConfig");
308                    } else {
309                        // see if seen in scan
310                        if (scanSeen[r + 1]) {
311                            return Bundle.getMessage("StatusPresent");
312                        } else {
313                            return Bundle.getMessage("StatusAbsent");
314                        }
315                    }
316                case EDITCOL:
317                    // see if node exists
318                    if (memo.getTrafficController().getNodeFromAddress(r + 1) != null) {
319                        return Bundle.getMessage("ButtonEdit");
320                    } else {
321                        return Bundle.getMessage("ButtonAdd");
322                    }
323                case INITCOL:
324                    // see if node exists
325                    if (memo.getTrafficController().getNodeFromAddress(r + 1) != null) {
326                        return Bundle.getMessage("ButtonInit");
327                    } else {
328                        return null;
329                    }
330
331                default:
332                    return null;
333            }
334        }
335
336        @Override
337        public void setValueAt(Object type, int r, int c) {
338            switch (c) {
339                case EDITCOL:
340                    NodeConfigFrame f = new NodeConfigFrame(memo);
341                    f.initComponents();
342                    f.setNodeAddress(r + 1);
343                    f.setVisible(true);
344                    return;
345                case INITCOL:
346                    jmri.jmrix.AbstractNode t = memo.getTrafficController().getNodeFromAddress(r + 1);
347                    memo.getTrafficController().sendSerialMessage((SerialMessage) t.createInitPacket(), null);
348                    return;
349                default:
350                    return;
351            }
352        }
353    }
354
355    private static final Logger log = LoggerFactory.getLogger(NodeTablePane.class);
356
357}