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     */
144    public void startPoll() {
145        // mark as none seen
146        for (int i = 0; i < 128; i++) {
147            scanSeen[i] = false;
148        }
149
150        status.setText(Bundle.getMessage("StatusStart"));
151
152        // create a timer to send messages
153        timer = new javax.swing.Timer(50, new java.awt.event.ActionListener() {
154            int node = 1;
155
156            @Override
157            public void actionPerformed(java.awt.event.ActionEvent e) {
158                // send message to node
159                log.debug("polling node {}", node);
160                status.setText(Bundle.getMessage("StatusRunningX", node, 127));
161                memo.getTrafficController().sendSerialMessage(SerialMessage.getPoll(node), null);
162                // done?
163                node++;
164                if (node >= 128) {
165                    // yes, stop
166                    timer.stop();
167                    timer = null;
168                    // if nothing seen yet, this is a failure
169                    if (status.getText().equals(Bundle.getMessage("StatusStart"))) {
170                        status.setText(Bundle.getMessage("StatusFail"));
171                    } else {
172                        status.setText(Bundle.getMessage("StatusOK"));
173                    }
174                }
175            }
176        });
177        timer.setInitialDelay(50);
178        timer.setRepeats(true);
179        timer.start();
180
181        // redisplay the table
182        nodesModel.fireTableDataChanged();
183    }
184
185    // indicate whether node has been seen
186    boolean scanSeen[] = new boolean[128];
187
188    /**
189     * Ignore messages being sent.
190     */
191    @Override
192    public void message(SerialMessage m) {
193    }
194
195    /**
196     * Listen for software version messages to know a node is present.
197     */
198    @Override
199    public void reply(SerialReply m) {
200        // set the status as having seen something
201        if (status.getText().equals(Bundle.getMessage("StatusStart"))) {
202            status.setText(Bundle.getMessage("StatusRunning"));
203        }
204        // is this a software version reply? if not, stop
205        if (m.getNumDataElements() != 2) {
206            return;
207        }
208        // does it have a real value?
209        // (getting 0x77 is just the original request)
210        if ((m.getElement(1) & 0xFF) == 0x77) {
211            return;
212        }
213        // yes, extract node number
214        int num = m.getElement(0) & 0x7F;
215        // mark as seen
216        scanSeen[num] = true;
217        // force redisplay of that line
218        nodesModel.fireTableRowsUpdated(num - 1, num - 1);
219    }
220
221    /**
222     * Set up table for selecting and showing nodes.
223     * <ol>
224     *   <li>Address
225     *   <li>Present Y/N
226     *   <li>Add/Edit button
227     * </ol>
228     */
229    public class NodesModel extends AbstractTableModel {
230        static private final int ADDRCOL = 0;
231        static private final int STATUSCOL = 1;
232        static private final int EDITCOL = 2;
233        static private final int INITCOL = 3;
234
235        static private final int LAST = 3;
236
237        @Override
238        public int getColumnCount() {
239            return LAST + 1;
240        }
241
242        @Override
243        public int getRowCount() {
244            return 127;
245        }
246
247        @Override
248        public String getColumnName(int c) {
249            switch (c) {
250                case ADDRCOL:
251                    return Bundle.getMessage("TitleAddress");
252                case STATUSCOL:
253                    return Bundle.getMessage("TitleStatus");
254                case EDITCOL:
255                    return ""; // no title over Add/Edit column
256                default:
257                    return "";
258            }
259        }
260
261        @Override
262        public Class<?> getColumnClass(int c) {
263            if (c == EDITCOL || c == INITCOL) {
264                return JButton.class;
265            } else if (c == ADDRCOL) {
266                return Integer.class;
267            } else {
268                return String.class;
269            }
270        }
271
272        @Override
273        public boolean isCellEditable(int r, int c) {
274            return (c == EDITCOL || c == INITCOL);
275        }
276
277        @Override
278        public Object getValueAt(int r, int c) {
279            // r is row number, from 0, and therefore r+1 is node number
280            switch (c) {
281                case ADDRCOL:
282                    return Integer.valueOf(r + 1);
283                case STATUSCOL:
284                    // see if node exists
285                    if (memo.getTrafficController().getNodeFromAddress(r + 1) != null) {
286                        return Bundle.getMessage("StatusConfig");
287                    } else {
288                        // see if seen in scan
289                        if (scanSeen[r + 1]) {
290                            return Bundle.getMessage("StatusPresent");
291                        } else {
292                            return Bundle.getMessage("StatusAbsent");
293                        }
294                    }
295                case EDITCOL:
296                    // see if node exists
297                    if (memo.getTrafficController().getNodeFromAddress(r + 1) != null) {
298                        return Bundle.getMessage("ButtonEdit");
299                    } else {
300                        return Bundle.getMessage("ButtonAdd");
301                    }
302                case INITCOL:
303                    // see if node exists
304                    if (memo.getTrafficController().getNodeFromAddress(r + 1) != null) {
305                        return Bundle.getMessage("ButtonInit");
306                    } else {
307                        return null;
308                    }
309
310                default:
311                    return null;
312            }
313        }
314
315        @Override
316        public void setValueAt(Object type, int r, int c) {
317            switch (c) {
318                case EDITCOL:
319                    NodeConfigFrame f = new NodeConfigFrame(memo);
320                    f.initComponents();
321                    f.setNodeAddress(r + 1);
322                    f.setVisible(true);
323                    return;
324                case INITCOL:
325                    jmri.jmrix.AbstractNode t = memo.getTrafficController().getNodeFromAddress(r + 1);
326                    memo.getTrafficController().sendSerialMessage((SerialMessage) t.createInitPacket(), null);
327                    return;
328                default:
329                    return;
330            }
331        }
332    }
333
334    private final static Logger log = LoggerFactory.getLogger(NodeTablePane.class);
335
336}