001package jmri.jmrit.symbolicprog; 002 003import java.awt.event.ActionEvent; 004import java.awt.event.ActionListener; 005import java.beans.PropertyChangeEvent; 006import java.beans.PropertyChangeListener; 007import java.util.Collections; 008import java.util.HashMap; 009import java.util.Set; 010import java.util.Vector; 011 012import javax.swing.JButton; 013import javax.swing.JLabel; 014import javax.swing.JTextField; 015import jmri.Programmer; 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019/** 020 * Table data model for display of CvValues in symbolic programmer. 021 * <p> 022 * This represents the contents of a single decoder, so the Programmer used to 023 * access it is a data member. 024 * 025 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2006 026 * @author Howard G. Penny Copyright (C) 2005 027 */ 028public class CvTableModel extends javax.swing.table.AbstractTableModel implements ActionListener, PropertyChangeListener { 029 030 private int _numRows = 0; // must be zero until Vectors are initialized 031 static final int MAXCVNUM = 1024; 032 private Vector<CvValue> _cvDisplayVector = new Vector<CvValue>(); // vector of CvValue objects, in display-row order, for doing row mapping 033 034 private HashMap<String, CvValue> _cvAllMap = new HashMap<String, CvValue>(); 035 036 public HashMap<String, CvValue> allCvMap() { 037 return _cvAllMap; 038 } 039 040 private Vector<JButton> _writeButtons = new Vector<JButton>(); 041 private Vector<JButton> _readButtons = new Vector<JButton>(); 042 private Vector<JButton> _compareButtons = new Vector<JButton>(); 043 private Programmer mProgrammer; 044 045 // Defines the columns 046 public static final int NUMCOLUMN = 0; 047 private static final int VALCOLUMN = 1; 048 private static final int STATECOLUMN = 2; 049 private static final int READCOLUMN = 3; 050 private static final int WRITECOLUMN = 4; 051 private static final int COMPARECOLUMN = 5; 052 053 private static final int HIGHESTCOLUMN = COMPARECOLUMN + 1; 054 private static final int HIGHESTNOPROG = STATECOLUMN + 1; 055 056 private JLabel _status = null; 057 058 public JLabel getStatusLabel() { 059 return _status; 060 } 061 062 public CvTableModel(JLabel status, Programmer pProgrammer) { 063 super(); 064 065 mProgrammer = pProgrammer; 066 // save a place for notification 067 _status = status; 068 069 // define just address CV at start, pending some variables 070 // boudreau: not sure why we need the statement below, 071 // messes up building CV table for CV #1 when in ops mode. 072 //addCV("1", false, false, false); 073 } 074 075 public void setNoDecoder() { 076 for (var b : _readButtons) { 077 b.setEnabled(false); 078 } 079 for (var b : _writeButtons) { 080 b.setEnabled(false); 081 } 082 for (var b : _compareButtons) { 083 b.setEnabled(false); 084 } 085 } 086 087 /** 088 * Gives access to the programmer used to reach these CVs, so you can check 089 * on mode, capabilities, etc. 090 * 091 * @return Programmer object for the CVs 092 */ 093 public Programmer getProgrammer() { 094 return mProgrammer; 095 } 096 097 public void setProgrammer(Programmer p) { 098 mProgrammer = p; 099 // tell all variables 100 for (CvValue cv : allCvMap().values()) { 101 if (cv != null) { 102 cv.setProgrammer(p); 103 } 104 } 105 for (CvValue cv : _cvDisplayVector) { 106 if (cv != null) { 107 cv.setProgrammer(p); 108 } 109 } 110 } 111 112 // basic methods for AbstractTableModel implementation 113 @Override 114 public int getRowCount() { 115 return _numRows; 116 } 117 118 @Override 119 public int getColumnCount() { 120 if (getProgrammer() != null) { 121 return HIGHESTCOLUMN; 122 } else { 123 return HIGHESTNOPROG; 124 } 125 } 126 127 @Override 128 public String getColumnName(int col) { 129 switch (col) { 130 case NUMCOLUMN: 131 return Bundle.getMessage("ColumnNameNumber"); 132 case VALCOLUMN: 133 return Bundle.getMessage("ColumnNameValue"); 134 case STATECOLUMN: 135 return Bundle.getMessage("ColumnNameState"); 136 case READCOLUMN: 137 return Bundle.getMessage("ColumnNameRead"); 138 case WRITECOLUMN: 139 return Bundle.getMessage("ColumnNameWrite"); 140 case COMPARECOLUMN: 141 return Bundle.getMessage("ColumnNameCompare"); 142 default: 143 return "unknown"; 144 } 145 } 146 147 @Override 148 public Class<?> getColumnClass(int col) { 149 switch (col) { 150 case NUMCOLUMN: 151 return Integer.class; 152 case VALCOLUMN: 153 return JTextField.class; 154 case STATECOLUMN: 155 return String.class; 156 case READCOLUMN: 157 return JButton.class; 158 case WRITECOLUMN: 159 return JButton.class; 160 case COMPARECOLUMN: 161 return JButton.class; 162 default: 163 return null; 164 } 165 } 166 167 @Override 168 public boolean isCellEditable(int row, int col) { 169 switch (col) { 170 case NUMCOLUMN: 171 return false; 172 case VALCOLUMN: 173 if (_cvDisplayVector.elementAt(row).getReadOnly() 174 || _cvDisplayVector.elementAt(row).getInfoOnly()) { 175 return false; 176 } else { 177 return true; 178 } 179 case STATECOLUMN: 180 return false; 181 case READCOLUMN: 182 return true; 183 case WRITECOLUMN: 184 return true; 185 case COMPARECOLUMN: 186 return true; 187 default: 188 return false; 189 } 190 } 191 192 public String getName(int row) { // name is text number 193 return "" + _cvDisplayVector.elementAt(row).number(); 194 } 195 196 public String getValString(int row) { 197 return "" + _cvDisplayVector.elementAt(row).getValue(); 198 } 199 200 public CvValue getCvByRow(int row) { 201 return _cvDisplayVector.elementAt(row); 202 } 203 204 public CvValue getCvByNumber(String number) { 205 return _cvAllMap.get(number); 206 } 207 208 @Override 209 public Object getValueAt(int row, int col) { 210 switch (col) { 211 case NUMCOLUMN: 212 return _cvDisplayVector.elementAt(row).number(); 213 case VALCOLUMN: 214 return _cvDisplayVector.elementAt(row).getTableEntry(); 215 case STATECOLUMN: 216 AbstractValue.ValueState state = _cvDisplayVector.elementAt(row).getState(); 217 switch (state) { 218 case UNKNOWN: 219 return Bundle.getMessage("CvStateUnknown"); 220 case READ: 221 return Bundle.getMessage("CvStateRead"); 222 case EDITED: 223 return Bundle.getMessage("CvStateEdited"); 224 case STORED: 225 return Bundle.getMessage("CvStateStored"); 226 case FROMFILE: 227 return Bundle.getMessage("CvStateFromFile"); 228 case FROMFILEUNKNOWN: 229 return Bundle.getMessage("CvStateFromFileUnknown"); 230 case SAME: 231 return Bundle.getMessage("CvStateSame"); 232 case DIFFERENT: 233 return Bundle.getMessage("CvStateDiff") + " " 234 + _cvDisplayVector.elementAt(row).getDecoderValue(); 235 case FROMDEFAULT: 236 return Bundle.getMessage("CvStateFromDefault"); 237 default: 238 return Bundle.getMessage("CvStateInconsistent"); 239 } 240 case READCOLUMN: 241 return _readButtons.elementAt(row); 242 case WRITECOLUMN: 243 return _writeButtons.elementAt(row); 244 case COMPARECOLUMN: 245 return _compareButtons.elementAt(row); 246 default: 247 return "unknown"; 248 } 249 } 250 251 @Override 252 public void setValueAt(Object value, int row, int col) { 253 switch (col) { 254 case VALCOLUMN: // Object is actually an Integer 255 if (_cvDisplayVector.elementAt(row).getValue() != ((Integer) value).intValue()) { 256 _cvDisplayVector.elementAt(row).setValue(((Integer) value).intValue()); 257 } 258 break; 259 default: 260 break; 261 } 262 } 263 264 @Override 265 public void actionPerformed(ActionEvent e) { 266 if (log.isDebugEnabled()) { 267 log.debug("action command: {}", e.getActionCommand()); 268 } 269 char b = e.getActionCommand().charAt(0); 270 int row = Integer.parseInt(e.getActionCommand().substring(1)); 271 if (log.isDebugEnabled()) { 272 log.debug("event on {} row {}", b, row); 273 } 274 if (b == 'R') { 275 // read command 276 _cvDisplayVector.elementAt(row).read(_status); 277 } else if (b == 'C') { 278 // compare command 279 _cvDisplayVector.elementAt(row).confirm(_status); 280 } else { 281 // write command 282 _cvDisplayVector.elementAt(row).write(_status); 283 } 284 } 285 286 @Override 287 public void propertyChange(PropertyChangeEvent e) { 288 // don't need to forward Busy, do need to forward Value 289 // not sure about any others 290 if (!e.getPropertyName().equals("Busy")) { 291 fireTableDataChanged(); 292 } 293 } 294 295 public void addCV(String s, boolean readOnly, boolean infoOnly, boolean writeOnly) { 296 if (_cvAllMap.get(s) == null) { 297 CvValue cv = new CvValue(s, mProgrammer); 298 cv.setReadOnly(readOnly); 299 _cvAllMap.put(s, cv); 300 _cvDisplayVector.addElement(cv); 301 // connect to this CV to ensure the table display updates 302 cv.addPropertyChangeListener(this); 303 JButton bw = new JButton(Bundle.getMessage("ButtonWrite")); 304 _writeButtons.addElement(bw); 305 JButton br = new JButton(Bundle.getMessage("ButtonRead")); 306 _readButtons.addElement(br); 307 JButton bc = new JButton(Bundle.getMessage("ButtonCompare")); 308 _compareButtons.addElement(bc); 309 if (infoOnly || readOnly) { 310 if (writeOnly) { 311 bw.setEnabled(true); 312 bw.setActionCommand("W" + _numRows); 313 bw.addActionListener(this); 314 } else { 315 bw.setEnabled(false); 316 } 317 if (infoOnly) { 318 br.setEnabled(false); 319 bc.setEnabled(false); 320 } else { 321 br.setEnabled(true); 322 br.setActionCommand("R" + _numRows); 323 br.addActionListener(this); 324 bc.setEnabled(true); 325 bc.setActionCommand("C" + _numRows); 326 bc.addActionListener(this); 327 } 328 } else { 329 bw.setEnabled(true); 330 bw.setActionCommand("W" + _numRows); 331 bw.addActionListener(this); 332 if (writeOnly) { 333 br.setEnabled(false); 334 bc.setEnabled(false); 335 } else { 336 br.setEnabled(true); 337 br.setActionCommand("R" + _numRows); 338 br.addActionListener(this); 339 bc.setEnabled(true); 340 bc.setActionCommand("C" + _numRows); 341 bc.addActionListener(this); 342 } 343 } 344 _numRows++; 345 fireTableDataChanged(); 346 } 347 // make sure readonly set true if required 348 CvValue cv = _cvAllMap.get(s); 349 if (readOnly) { 350 cv.setReadOnly(readOnly); 351 } 352 if (infoOnly) { 353 cv.setReadOnly(!infoOnly); 354 cv.setWriteOnly(!infoOnly); 355 cv.setInfoOnly(infoOnly); 356 } 357 if (writeOnly) { 358 cv.setWriteOnly(writeOnly); 359 } 360 } 361 362 public boolean decoderDirty() { 363 int len = _cvDisplayVector.size(); 364 for (int i = 0; i < len; i++) { 365 if (_cvDisplayVector.elementAt(i).getState() == AbstractValue.ValueState.EDITED) { 366 if (log.isDebugEnabled()) { 367 log.debug("CV decoder dirty due to {}", _cvDisplayVector.elementAt(i).number()); 368 } 369 return true; 370 } 371 } 372 return false; 373 } 374 375 /** 376 * Register a VariableValue in a common store mapping CV numbers to 377 * variable names. This is for use by e.g. a CVTable to show tooltips 378 * efficiently. 379 * @param cv specific CV number that the variable references 380 * @param variableName from the variable being defined 381 */ 382 public void registerCvToVariableMapping(String cv, String variableName) { 383 // is there already a Set for these? 384 if ( ! cvToVarMap.containsKey(cv)) { 385 // no, create one 386 cvToVarMap.put(cv, Collections.newSetFromMap(new HashMap<String, Boolean>())); 387 } 388 // add the String 389 cvToVarMap.get(cv).add(variableName); 390 } 391 392 public Set<String> getCvToVariableMapping(String cv) { return cvToVarMap.get(cv); } 393 394 private HashMap<String, Set<String>> cvToVarMap = new HashMap<>(); 395 396 public void dispose() { 397 if (log.isDebugEnabled()) { 398 log.debug("dispose"); 399 } 400 401 // remove buttons 402 for (int i = 0; i < _writeButtons.size(); i++) { 403 _writeButtons.elementAt(i).removeActionListener(this); 404 } 405 for (int i = 0; i < _readButtons.size(); i++) { 406 _readButtons.elementAt(i).removeActionListener(this); 407 } 408 for (int i = 0; i < _compareButtons.size(); i++) { 409 _compareButtons.elementAt(i).removeActionListener(this); 410 } 411 412 // remove CV listeners 413 for (int i = 0; i < _cvDisplayVector.size(); i++) { 414 _cvDisplayVector.elementAt(i).removePropertyChangeListener(this); 415 } 416 417 // null references, so that they can be gc'd even if this isn't. 418 cvToVarMap = null; 419 420 _cvDisplayVector.removeAllElements(); 421 _cvDisplayVector = null; 422 423 _writeButtons.removeAllElements(); 424 _writeButtons = null; 425 426 _readButtons.removeAllElements(); 427 _readButtons = null; 428 429 _compareButtons.removeAllElements(); 430 _compareButtons = null; 431 432 _cvAllMap.clear(); 433 _cvAllMap = null; 434 435 _status = null; 436 437 } 438 439 int holdsAddress() { 440 int shortAddr = getCvByNumber("1").getValue(); 441 int longAddr = ((getCvByNumber("17").getValue()-192)<<8)+getCvByNumber("18").getValue(); 442 int addr = holdsLongAddress() ? longAddr : shortAddr; 443 return addr; 444 } 445 446 boolean holdsLongAddress() { 447 return (getCvByNumber("29").getValue() & 0x20) != 0; 448 } 449 450 private static final Logger log = LoggerFactory.getLogger(CvTableModel.class); 451}