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}