001package jmri.jmrix.can.cbus.node; 002 003import java.beans.PropertyChangeListener; 004import java.beans.PropertyChangeEvent; 005import java.util.Arrays; 006 007import jmri.jmrix.can.CanSystemConnectionMemo; 008import jmri.jmrix.can.cbus.swing.modules.CbusConfigPaneProvider; 009import jmri.util.StringUtil; 010import jmri.util.ThreadingUtil; 011 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015/** 016 * Table data model for display of CBUS Node Variables. 017 * 018 * @author Steve Young (c) 2019 019 * 020 */ 021public class CbusNodeNVTableDataModel extends javax.swing.table.AbstractTableModel 022 implements PropertyChangeListener { 023 024 private int[] newNVs; 025 private CbusNode nodeOfInterest; 026 027 // column order needs to match list in column tooltips 028 public static final int NV_NUMBER_COLUMN = 0; 029 public static final int NV_NAME_COLUMN = 1; 030 public static final int NV_CURRENT_VAL_COLUMN = 2; 031 public static final int NV_CURRENT_HEX_COLUMN = 3; 032 public static final int NV_CURRENT_BIT_COLUMN = 4; 033 public static final int NV_SELECT_COLUMN = 5; 034 public static final int NV_SELECT_HEX_COLUMN = 6; 035 public static final int NV_SELECT_BIT_COLUMN = 7; 036 public static final int MAX_COLUMN = 8; 037 038 public CbusNodeNVTableDataModel(CanSystemConnectionMemo memo, int row, int column ) { 039 log.debug("Starting MERG CBUS Node NV Table"); 040 } 041 042 /** {@inheritDoc} */ 043 @Override 044 public void propertyChange(PropertyChangeEvent ev){ 045 if (ev.getPropertyName().equals("SINGLENVUPDATE")) { 046 int newValue = (Integer) ev.getNewValue(); 047// resetNewNvs(); // Why do this for a single NV? 048 log.debug("SINGLENVUPDATE {}", newValue); 049 resetSingleNv(newValue); 050 fireTableRowsUpdated(newValue,newValue); 051 } 052 else if (ev.getPropertyName().equals("ALLNVUPDATE")) { 053 log.debug("ALLNVUPDATE"); 054 resetNewNvs(); 055 fireTableDataChanged(); 056 } 057 } 058 059 /** 060 * Return the number of rows to be displayed. 061 * {@inheritDoc} 062 */ 063 @Override 064 public int getRowCount() { 065 try { 066 return nodeOfInterest.getNodeNvManager().getTotalNVs(); 067 } catch (NullPointerException e) { 068 return 0; 069 } 070 } 071 072 /** 073 * {@inheritDoc} 074 */ 075 @Override 076 public int getColumnCount() { 077 return MAX_COLUMN; 078 } 079 080 /** 081 * Returns String of column name from column int 082 * used in table header 083 * {@inheritDoc} 084 * @param col int col number 085 */ 086 @Override 087 public String getColumnName(int col) { // not in any order 088 switch (col) { 089 case NV_NUMBER_COLUMN: 090 return ("NV"); 091 case NV_NAME_COLUMN: 092 return ("Name"); 093 case NV_CURRENT_VAL_COLUMN: 094 return ("Dec."); 095 case NV_CURRENT_HEX_COLUMN: 096 return ("Hex."); 097 case NV_CURRENT_BIT_COLUMN: 098 return ("Bin."); 099 case NV_SELECT_COLUMN: 100 return ("New Dec."); 101 case NV_SELECT_HEX_COLUMN: 102 return ("New Hex."); 103 case NV_SELECT_BIT_COLUMN: 104 return("New Bin."); 105 default: 106 return "unknown " + col; // NOI18N 107 } 108 } 109 110 /** 111 * Returns column class type. 112 * {@inheritDoc} 113 */ 114 @Override 115 public Class<?> getColumnClass(int col) { 116 switch (col) { 117 case NV_SELECT_HEX_COLUMN: 118 case NV_NAME_COLUMN: 119 case NV_SELECT_BIT_COLUMN: 120 case NV_CURRENT_HEX_COLUMN: 121 case NV_CURRENT_BIT_COLUMN: 122 return String.class; 123 default: 124 return Integer.class; 125 } 126 } 127 128 /** 129 * boolean return to edit table cell or not 130 * {@inheritDoc} 131 * @return boolean 132 */ 133 @Override 134 public boolean isCellEditable(int row, int col) { 135 switch (col) { 136 case NV_SELECT_COLUMN: 137 case NV_SELECT_HEX_COLUMN: 138 case NV_SELECT_BIT_COLUMN: 139 return true; 140 default: 141 return false; 142 } 143 } 144 145 /** 146 * Return table values 147 * {@inheritDoc} 148 * @param row int row number 149 * @param col int col number 150 */ 151 @Override 152 public Object getValueAt(int row, int col) { 153 154 if ( nodeOfInterest.getNodeNvManager().getTotalNVs() < 1 ) { 155 return null; 156 } 157 158 switch (col) { 159 case NV_NUMBER_COLUMN: 160 return (row +1); 161 case NV_NAME_COLUMN: 162 return CbusConfigPaneProvider.getProviderByNode(nodeOfInterest).getNVNameByIndex(row + 1); // NV indices start at 1 163 case NV_CURRENT_VAL_COLUMN: 164 return nodeOfInterest.getNodeNvManager().getNV(row+1); 165 case NV_CURRENT_HEX_COLUMN: 166 if ( nodeOfInterest.getNodeNvManager().getNV(row+1) > -1 ) { 167 return StringUtil.twoHexFromInt(nodeOfInterest.getNodeNvManager().getNV(row+1)); 168 } 169 else { 170 break; 171 } 172 case NV_CURRENT_BIT_COLUMN: 173 int num = nodeOfInterest.getNodeNvManager().getNV(row+1); 174 if ( num > -1 ) { 175 return (String.format("%8s", Integer.toBinaryString(num)).replace(' ', '0')).substring(0,4) + " " + 176 (String.format("%8s", Integer.toBinaryString(num)).replace(' ', '0')).substring(4,8); 177 } 178 else { 179 break; 180 } 181 case NV_SELECT_COLUMN: 182 if ( newNVs.length < row+1) { 183 return 0; 184 } 185 if ( newNVs[(row+1)] > -1 ) { 186 return newNVs[(row+1)]; 187 } else { 188 return nodeOfInterest.getNodeNvManager().getNV(row+1); 189 } 190 case NV_SELECT_HEX_COLUMN: 191 if ( newNVs.length <= row+1) { 192 break; 193 } 194 if (newNVs[(row+1)]>-1) { 195 return StringUtil.twoHexFromInt(newNVs[(row+1)]); 196 } 197 else { 198 break; 199 } 200 case NV_SELECT_BIT_COLUMN: 201 if ( newNVs.length <= row+1) { 202 break; 203 } 204 if (newNVs[(row+1)]>-1) { 205 return (String.format("%8s", Integer.toBinaryString(newNVs[(row+1)])).replace(' ', '0')).substring(0,4) + " " + 206 (String.format("%8s", Integer.toBinaryString(newNVs[(row+1)])).replace(' ', '0')).substring(4,8); 207 } else { 208 break; 209 } 210 default: 211 return null; 212 } 213 return ""; 214 } 215 216 /** 217 * {@inheritDoc} 218 */ 219 @Override 220 public void setValueAt(Object value, int row, int col) { 221 log.debug("set value {} row {} col {}",value,row,col); 222 if ( newNVs.length ==0 || row < 0 || row + 1 >= newNVs.length ) { 223 return; 224 } 225 int newval; 226 switch (col) { 227 case NV_SELECT_COLUMN: 228 newval = (int) value; 229 break; 230 case NV_SELECT_HEX_COLUMN: 231 newval = StringUtil.getByte(0, ((String) value).trim()); 232 break; 233 case NV_SELECT_BIT_COLUMN: 234 try { 235 newval = Integer.parseInt(((String) value).replaceAll("\\s+",""), 2); 236 } 237 catch ( NumberFormatException e ){ 238 return; 239 } 240 if (newval < 0 || newval > 255) { 241 return; 242 } 243 break; 244 default: 245 return; 246 } 247 if ( newNVs[(row+1)] == newval ) { 248 // no change, do not trigger table updates 249 return; 250 } 251 newNVs[(row+1)] = newval; 252 ThreadingUtil.runOnGUIEventually(() -> fireTableRowsUpdated(row,row)); 253 } 254 255 /** 256 * Set the Node to be used in table. 257 * @param node the CbusNode of Interest to the NV Table 258 */ 259 public void setNode( CbusNode node){ 260 log.debug("setting array for node {}",node); 261 262 if ( nodeOfInterest != null ) { 263 nodeOfInterest.removePropertyChangeListener(this); 264 nodeOfInterest.setliveUpdate(false); 265 } 266 267 nodeOfInterest = node; 268 269 if ( nodeOfInterest == null ) { 270 return; 271 } 272 273 resetNewNvs(); 274 nodeOfInterest.addPropertyChangeListener(this); 275 fireTableDataChanged(); 276 277 } 278 279 /** 280 * Get the Node being used in table. 281 * 282 * @return the CbusNode of Interest 283 */ 284 public CbusNode getNode() { 285 return nodeOfInterest; 286 } 287 288 /** 289 * Checks if a single NV has been edited to a new value 290 * @param nvToCheck the single NV to check 291 * @return true if dirty, else false 292 */ 293 public boolean isSingleNvDirty( int nvToCheck ) { 294 return ( (int) getValueAt(nvToCheck,NV_CURRENT_VAL_COLUMN) ) != ( 295 (int) getValueAt(nvToCheck,NV_SELECT_COLUMN) ); 296 } 297 298 /** 299 * Checks if any NV has been edited to a new value 300 * @return true if any NV has been edited, else false 301 */ 302 public boolean isTableDirty() { 303 try { 304 for (int i = 0; i < getRowCount(); i++) { 305 if ( isSingleNvDirty(i) ) { 306 return true; 307 } 308 } 309 return false; 310 } 311 catch ( NullPointerException e ){ 312 return false; 313 } 314 } 315 316 /** 317 * Get count of changed NVs. 318 * @return number of changed NVs 319 */ 320 public int getCountDirty() { 321 int count = 0; 322 for (int i = 0; i < getRowCount(); i++) { 323 if ( isSingleNvDirty(i) ) { 324 count++; 325 } 326 } 327 return count; 328 } 329 330 /** 331 * Resets the edit NV value to match the actual NV value. 332 */ 333 public void resetNewNvs() { 334 335 // setup a new fixed length array to hold new nv values 336 if ( nodeOfInterest.getNodeNvManager().getNvArray() == null ) { 337 log.debug("Create newNVs[] of length 0"); 338 newNVs = new int[0]; 339 } 340 else { 341 log.debug("Create newNVs[] of length {}", nodeOfInterest.getNodeNvManager().getNvArray().length); 342 newNVs = new int[ ( nodeOfInterest.getNodeNvManager().getNvArray().length ) ]; 343 newNVs = Arrays.copyOf( 344 nodeOfInterest.getNodeNvManager().getNvArray(), 345 nodeOfInterest.getNodeNvManager().getNvArray().length); 346 } 347 348 for (int i = 0; i < getRowCount(); i++) { 349 350 setValueAt( getValueAt(i,NV_CURRENT_VAL_COLUMN), i, NV_SELECT_COLUMN); 351 } 352 } 353 354 /** 355 * Resets a single edit NV value to match the actual NV value. 356 * 357 * @param row row to reset 358 */ 359 public void resetSingleNv(int row) { 360 setValueAt( getValueAt(row,NV_CURRENT_VAL_COLUMN), row, NV_SELECT_COLUMN); 361 } 362 363 /** 364 * Get a backup node containing the edited NVs. 365 * @return a node which has the new NV's 366 */ 367 public CbusNodeFromBackup getChangedNode(){ 368 CbusNodeFromBackup temp = new CbusNodeFromBackup(nodeOfInterest,null); 369 temp.getNodeNvManager().setNVs(newNVs); 370 return temp; 371 } 372 373 /** 374 * De-registers the NV Table from receiving updates from the CbusNode. 375 */ 376 public void dispose(){ 377 if ( nodeOfInterest != null ) { 378 nodeOfInterest.removePropertyChangeListener(this); 379 nodeOfInterest.setliveUpdate(false); 380 } 381 } 382 383 private static final Logger log = LoggerFactory.getLogger(CbusNodeNVTableDataModel.class); 384}