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}