001package jmri.jmrix.can.cbus.swing.eventrequestmonitor; 002 003import javax.swing.JButton; 004import javax.swing.JCheckBox; 005 006import java.util.ArrayList; 007import java.util.Date; 008 009import jmri.jmrix.can.CanListener; 010import jmri.jmrix.can.CanMessage; 011import jmri.jmrix.can.CanReply; 012import jmri.jmrix.can.CanSystemConnectionMemo; 013import jmri.jmrix.can.cbus.CbusMessage; 014import jmri.jmrix.can.cbus.CbusNameService; 015import jmri.jmrix.can.cbus.CbusOpCodes; 016import jmri.jmrix.can.TrafficController; 017import jmri.jmrix.can.cbus.CbusEvent; 018import jmri.util.swing.TextAreaFIFO; 019import jmri.util.swing.JmriJOptionPane; 020import jmri.util.ThreadingUtil; 021 022/** 023 * Table data model for display of Cbus request events 024 * 025 * @author Steve Young (c) 2018 026 * 027 */ 028public class CbusEventRequestDataModel extends javax.swing.table.AbstractTableModel implements CanListener { 029 030 private boolean sessionConfirmDeleteRow=true; // display confirm popup 031 private final int _defaultFeedback= 1; 032 protected int _contype=0; // event table pane console message type 033 protected String _context; // event table pane console text 034 private final int _defaultfeedbackdelay = 4000; 035 private static final int MAX_LINES = 500; // tablefeedback screen log size 036 037 protected ArrayList<CbusEventRequestMonitorEvent> _mainArray; 038 private final CbusNameService nameService; 039 protected TextAreaFIFO tablefeedback; 040 // private CanSystemConnectionMemo _memo; 041 private final TrafficController tc; 042 043 // column order needs to match list in column tooltips 044 static public final int EVENT_COLUMN = 0; 045 static public final int NODE_COLUMN = 1; 046 static public final int NAME_COLUMN = 2; 047 static public final int LATEST_TIMESTAMP_COLUMN = 3; 048 static public final int STATUS_REQUEST_BUTTON_COLUMN = 4; 049 static public final int LASTFEEDBACK_COLUMN = 5; 050 static public final int FEEDBACKOUTSTANDING_COLUMN = 6; 051 static public final int FEEDBACKREQUIRED_COLUMN = 7; 052 static public final int FEEDBACKTIMEOUT_COLUMN = 8; 053 static public final int FEEDBACKEVENT_COLUMN = 9; 054 static public final int FEEDBACKNODE_COLUMN = 10; 055 static public final int DELETE_BUTTON_COLUMN = 11; 056 057 static public final int MAX_COLUMN = 12; 058 059 CbusEventRequestDataModel(CanSystemConnectionMemo memo, int row, int column) { 060 061 _mainArray = new ArrayList<>(); 062 tablefeedback = new TextAreaFIFO(MAX_LINES); 063 // _memo = memo; 064 tc = memo.getTrafficController(); 065 addTc(tc); 066 nameService = new CbusNameService(memo); 067 } 068 069 // order needs to match column list top of dtabledatamodel 070 static final String[] columnToolTips = { 071 Bundle.getMessage("EventColTip"), 072 Bundle.getMessage("NodeColTip"), 073 Bundle.getMessage("NameColTip"), 074 Bundle.getMessage("ColumnLastHeard") + Bundle.getMessage("TypeColTip"), 075 Bundle.getMessage("ColumnRequestStatusTip"), 076 Bundle.getMessage("FBLastTip"), 077 Bundle.getMessage("FBOutstandingTip"), 078 Bundle.getMessage("FBNumTip"), 079 Bundle.getMessage("FBTimeoutTip"), 080 Bundle.getMessage("FBEventTip"), 081 Bundle.getMessage("FBNodeTip"), 082 Bundle.getMessage("ColumnEventDeleteTip") 083 084 }; // Length = number of items in array should (at least) match number of columns 085 086 /** 087 * Return the number of rows to be displayed. 088 */ 089 @Override 090 public int getRowCount() { 091 return _mainArray.size(); 092 } 093 094 @Override 095 public int getColumnCount() { 096 return MAX_COLUMN; 097 } 098 099 /** 100 * Returns String of column name from column int 101 * used in table header 102 * @param col int col number 103 */ 104 @Override 105 public String getColumnName(int col) { // not in any order 106 switch (col) { 107 case NODE_COLUMN: 108 return Bundle.getMessage("CbusNode"); 109 case NAME_COLUMN: 110 return Bundle.getMessage("ColumnName"); 111 case EVENT_COLUMN: 112 return Bundle.getMessage("CbusEvent"); 113 case DELETE_BUTTON_COLUMN: 114 return Bundle.getMessage("ColumnEventDelete"); 115 case STATUS_REQUEST_BUTTON_COLUMN: 116 return Bundle.getMessage("ColumnStatusRequest"); 117 case LATEST_TIMESTAMP_COLUMN: 118 return Bundle.getMessage("ColumnLastHeard"); 119 case LASTFEEDBACK_COLUMN: 120 return Bundle.getMessage("FBLast"); 121 case FEEDBACKREQUIRED_COLUMN: 122 return Bundle.getMessage("FBRequired"); 123 case FEEDBACKOUTSTANDING_COLUMN: 124 return Bundle.getMessage("FBOutstanding"); 125 case FEEDBACKEVENT_COLUMN: 126 return Bundle.getMessage("FBEvent"); 127 case FEEDBACKNODE_COLUMN: 128 return Bundle.getMessage("FBNode"); 129 case FEEDBACKTIMEOUT_COLUMN: 130 return Bundle.getMessage("FBTimeout"); 131 default: 132 return "unknown"; // NOI18N 133 } 134 } 135 136 /** 137 * Returns column class type. 138 * {@inheritDoc} 139 */ 140 @Override 141 public Class<?> getColumnClass(int col) { 142 switch (col) { 143 case EVENT_COLUMN: 144 case NODE_COLUMN: 145 case FEEDBACKREQUIRED_COLUMN: 146 case FEEDBACKOUTSTANDING_COLUMN: 147 case FEEDBACKEVENT_COLUMN: 148 case FEEDBACKNODE_COLUMN: 149 case FEEDBACKTIMEOUT_COLUMN: 150 return Integer.class; 151 case NAME_COLUMN: 152 return String.class; 153 case DELETE_BUTTON_COLUMN: 154 case STATUS_REQUEST_BUTTON_COLUMN: 155 return JButton.class; 156 case LATEST_TIMESTAMP_COLUMN: 157 return Date.class; 158 case LASTFEEDBACK_COLUMN: 159 return Enum.class; 160 default: 161 return null; 162 } 163 } 164 165 /** 166 * Boolean return to edit table cell or not 167 * {@inheritDoc} 168 * @return boolean 169 */ 170 @Override 171 public boolean isCellEditable(int row, int col) { 172 switch (col) { 173 case DELETE_BUTTON_COLUMN: 174 case STATUS_REQUEST_BUTTON_COLUMN: 175 case FEEDBACKREQUIRED_COLUMN: 176 case FEEDBACKEVENT_COLUMN: 177 case FEEDBACKNODE_COLUMN: 178 case FEEDBACKTIMEOUT_COLUMN: 179 return true; 180 default: 181 return false; 182 } 183 } 184 185 /** 186 * Return table values 187 * @param row int row number 188 * @param col int col number 189 */ 190 @Override 191 public Object getValueAt(int row, int col) { 192 switch (col) { 193 case NODE_COLUMN: 194 return _mainArray.get(row).getNn(); 195 case EVENT_COLUMN: 196 return _mainArray.get(row).getEn(); 197 case NAME_COLUMN: 198 return nameService.getEventName(_mainArray.get(row).getNn(),_mainArray.get(row).getEn() ); 199 case STATUS_REQUEST_BUTTON_COLUMN: 200 return Bundle.getMessage("StatusButton"); 201 case DELETE_BUTTON_COLUMN: 202 return Bundle.getMessage("ButtonDelete"); 203 case LATEST_TIMESTAMP_COLUMN: 204 return _mainArray.get(row).getDate(); 205 case LASTFEEDBACK_COLUMN: 206 return _mainArray.get(row).getLastFb(); 207 case FEEDBACKREQUIRED_COLUMN: 208 return _mainArray.get(row).getFeedbackTotReqd(); 209 case FEEDBACKOUTSTANDING_COLUMN: 210 return _mainArray.get(row).getFeedbackOutstanding(); 211 case FEEDBACKEVENT_COLUMN: 212 return _mainArray.get(row).getExtraEvent(); 213 case FEEDBACKNODE_COLUMN: 214 return _mainArray.get(row).getExtraNode(); 215 case FEEDBACKTIMEOUT_COLUMN: 216 return _mainArray.get(row).getFeedbackTimeout(); 217 default: 218 return null; 219 } 220 } 221 222 /** 223 * Capture new comments or node names. 224 * Button events 225 * @param value object value 226 * @param row int row number 227 * @param col int col number 228 */ 229 @Override 230 public void setValueAt(Object value, int row, int col) { 231 // log.debug("427 set valueat called row: {} col: {}", row, col); 232 switch (col) { 233 case DELETE_BUTTON_COLUMN: 234 buttonDeleteClicked(row); 235 break; 236 case STATUS_REQUEST_BUTTON_COLUMN: 237 _mainArray.get(row).sendEvent(CbusEventRequestMonitorEvent.EvState.REQUEST); // gui updates from outgoing msg 238 break; 239 case LATEST_TIMESTAMP_COLUMN: 240 _mainArray.get(row).setDate( new Date() ); 241 updateGui(row, col); 242 break; 243 case FEEDBACKREQUIRED_COLUMN: 244 _mainArray.get(row).setFeedbackTotReqd( (int) value ); 245 updateGui(row, col); 246 break; 247 case FEEDBACKOUTSTANDING_COLUMN: 248 _mainArray.get(row).setFeedbackOutstanding( (Integer) value ); 249 updateGui(row, col); 250 break; 251 case FEEDBACKEVENT_COLUMN: 252 _mainArray.get(row).setExtraEvent( (int) value ); 253 updateGui(row, col); 254 break; 255 case FEEDBACKNODE_COLUMN: 256 _mainArray.get(row).setExtraNode( (int) value ); 257 updateGui(row, col); 258 break; 259 case FEEDBACKTIMEOUT_COLUMN: 260 _mainArray.get(row).setFeedbackTimeout( (int) value ); 261 updateGui(row, col); 262 break; 263 case LASTFEEDBACK_COLUMN: 264 _mainArray.get(row).setLastFb( (CbusEventRequestMonitorEvent.FbState) value ); 265 updateGui(row, col); 266 break; 267 default: 268 break; 269 } 270 } 271 272 private void updateGui(int row, int col){ 273 ThreadingUtil.runOnGUIEventually( ()->{ 274 fireTableCellUpdated(row, col); 275 }); 276 } 277 278 // outgoing cbus message 279 // or incoming CanReply 280 @Override 281 public void message(CanMessage m) { 282 if ( m.extendedOrRtr() ) { 283 return; 284 } 285 int opc = CbusMessage.getOpcode(m); 286 if (CbusOpCodes.isEventRequest(opc)) { 287 processEvRequest( CbusMessage.getNodeNumber(m) , CbusMessage.getEvent(m) ); 288 } 289 else if (CbusOpCodes.isEventNotRequest(opc)) { 290 processEvent( CbusMessage.getNodeNumber(m) , CbusMessage.getEvent(m) ); 291 } 292 } 293 294 // incoming cbus message 295 // handled the same as outgoing 296 @Override 297 public void reply(CanReply r) { 298 if ( r.extendedOrRtr() ) { 299 return; 300 } 301 CanMessage m = new CanMessage(r); 302 message(m); 303 } 304 305 // called when event heard as CanReply / CanMessage 306 private void processEvent( int nn, int en ){ 307 308 int existingRow = eventRow( nn, en); 309 int fbRow = extraFeedbackRow( nn, en); 310 if ( existingRow > -1 ) { 311 _mainArray.get(existingRow).setResponseReceived(); 312 setValueAt(1, existingRow, LATEST_TIMESTAMP_COLUMN); 313 } 314 else if ( fbRow > -1 ){ 315 _mainArray.get(fbRow).setResponseReceived(); 316 } 317 } 318 319 // called when request heard as CanReply / CanMessage 320 private void processEvRequest( int nn, int en ) { 321 322 int existingRow = eventRow( nn, en); 323 if (existingRow<0) { 324 addEvent(nn,en,CbusEventRequestMonitorEvent.EvState.REQUEST,null); 325 } 326 existingRow = eventRow( nn, en); 327 _mainArray.get(existingRow).setRequestReceived(); 328 329 } 330 331 protected int eventRow(int nn, int en) { 332 return _mainArray.indexOf(new CbusEvent(nn,en)); 333 } 334 335 protected int extraFeedbackRow(int nn, int en) { 336 for (int i = 0; i < getRowCount(); i++) { 337 if (_mainArray.get(i).matchesFeedback(nn, en)) { 338 return i; 339 } 340 } 341 return -1; 342 } 343 344 public void addEvent(int node, int event, CbusEventRequestMonitorEvent.EvState state, Date timestamp) { 345 346 CbusEventRequestMonitorEvent newmonitor = new CbusEventRequestMonitorEvent( 347 node, event, state, timestamp, _defaultfeedbackdelay, 348 _defaultFeedback, this ); 349 350 _mainArray.add(newmonitor); 351 352 ThreadingUtil.runOnGUI( ()->{ fireTableRowsInserted((getRowCount()-1), (getRowCount()-1)); }); 353 addToLog(1,newmonitor.toString() + Bundle.getMessage("AddedToTable")); 354 } 355 356 /** 357 * Remove Row from table 358 * @see #buttonDeleteClicked 359 * @param row int row number 360 */ 361 private void removeRow(int row) { 362 _context = _mainArray.get(row).toString() + Bundle.getMessage("TableConfirmDelete"); 363 _mainArray.remove(row); 364 ThreadingUtil.runOnGUI( ()->{ fireTableRowsDeleted(row,row); }); 365 addToLog(3,_context); 366 } 367 368 /** 369 * Delete Button Clicked 370 * See whether to display confirm popup 371 * @see #removeRow 372 * @param row int row number 373 */ 374 private void buttonDeleteClicked(int row) { 375 if (sessionConfirmDeleteRow) { 376 // confirm deletion with the user 377 JCheckBox checkbox = new JCheckBox(Bundle.getMessage("PopupSessionConfirmDel")); 378 String message = Bundle.getMessage("DelConfirmOne") + "\n" 379 + Bundle.getMessage("DelConfirmTwo"); 380 Object[] params = {message, checkbox}; 381 382 if (JmriJOptionPane.OK_OPTION == JmriJOptionPane.showConfirmDialog( 383 null, params, Bundle.getMessage("DelEvPopTitle"), 384 JmriJOptionPane.OK_CANCEL_OPTION, 385 JmriJOptionPane.WARNING_MESSAGE)) { 386 boolean dontShow = checkbox.isSelected(); 387 if (dontShow) { 388 sessionConfirmDeleteRow=false; 389 } 390 removeRow(row); 391 } 392 } else { 393 // no need to show warning, just delete 394 removeRow(row); 395 } 396 } 397 398 /** 399 * Add to Event Table Console Log 400 * @param cbuserror int 401 * @param cbustext String console message 402 */ 403 public void addToLog(int cbuserror, String cbustext){ 404 ThreadingUtil.runOnGUI( ()->{ 405 if (cbuserror==3) { 406 tablefeedback.append ("\n * * * * * * * * * * * * * * * * * * * * * * " + cbustext); 407 } else { 408 tablefeedback.append( "\n"+cbustext); 409 } 410 }); 411 } 412 413 /** 414 * disconnect from the CBUS 415 */ 416 public void dispose() { 417 // eventTable.removeAllElements(); 418 // eventTable = null; 419 for (int i = 0; i < getRowCount(); i++) { 420 _mainArray.get(i).stopTheTimer(); 421 } 422 _mainArray = null; 423 424 tablefeedback.dispose(); 425 tc.removeCanListener(this); 426 427 } 428 429 protected TextAreaFIFO tablefeedback(){ 430 return tablefeedback; 431 } 432 433 // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusEventRequestDataModel.class); 434 435}