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 SAME: 229 return Bundle.getMessage("CvStateSame"); 230 case DIFFERENT: 231 return Bundle.getMessage("CvStateDiff") + " " 232 + _cvDisplayVector.elementAt(row).getDecoderValue(); 233 default: 234 return "inconsistent"; 235 } 236 case READCOLUMN: 237 return _readButtons.elementAt(row); 238 case WRITECOLUMN: 239 return _writeButtons.elementAt(row); 240 case COMPARECOLUMN: 241 return _compareButtons.elementAt(row); 242 default: 243 return "unknown"; 244 } 245 } 246 247 @Override 248 public void setValueAt(Object value, int row, int col) { 249 switch (col) { 250 case VALCOLUMN: // Object is actually an Integer 251 if (_cvDisplayVector.elementAt(row).getValue() != ((Integer) value).intValue()) { 252 _cvDisplayVector.elementAt(row).setValue(((Integer) value).intValue()); 253 } 254 break; 255 default: 256 break; 257 } 258 } 259 260 @Override 261 public void actionPerformed(ActionEvent e) { 262 if (log.isDebugEnabled()) { 263 log.debug("action command: {}", e.getActionCommand()); 264 } 265 char b = e.getActionCommand().charAt(0); 266 int row = Integer.parseInt(e.getActionCommand().substring(1)); 267 if (log.isDebugEnabled()) { 268 log.debug("event on {} row {}", b, row); 269 } 270 if (b == 'R') { 271 // read command 272 _cvDisplayVector.elementAt(row).read(_status); 273 } else if (b == 'C') { 274 // compare command 275 _cvDisplayVector.elementAt(row).confirm(_status); 276 } else { 277 // write command 278 _cvDisplayVector.elementAt(row).write(_status); 279 } 280 } 281 282 @Override 283 public void propertyChange(PropertyChangeEvent e) { 284 // don't need to forward Busy, do need to forward Value 285 // not sure about any others 286 if (!e.getPropertyName().equals("Busy")) { 287 fireTableDataChanged(); 288 } 289 } 290 291 public void addCV(String s, boolean readOnly, boolean infoOnly, boolean writeOnly) { 292 if (_cvAllMap.get(s) == null) { 293 CvValue cv = new CvValue(s, mProgrammer); 294 cv.setReadOnly(readOnly); 295 _cvAllMap.put(s, cv); 296 _cvDisplayVector.addElement(cv); 297 // connect to this CV to ensure the table display updates 298 cv.addPropertyChangeListener(this); 299 JButton bw = new JButton(Bundle.getMessage("ButtonWrite")); 300 _writeButtons.addElement(bw); 301 JButton br = new JButton(Bundle.getMessage("ButtonRead")); 302 _readButtons.addElement(br); 303 JButton bc = new JButton(Bundle.getMessage("ButtonCompare")); 304 _compareButtons.addElement(bc); 305 if (infoOnly || readOnly) { 306 if (writeOnly) { 307 bw.setEnabled(true); 308 bw.setActionCommand("W" + _numRows); 309 bw.addActionListener(this); 310 } else { 311 bw.setEnabled(false); 312 } 313 if (infoOnly) { 314 br.setEnabled(false); 315 bc.setEnabled(false); 316 } else { 317 br.setEnabled(true); 318 br.setActionCommand("R" + _numRows); 319 br.addActionListener(this); 320 bc.setEnabled(true); 321 bc.setActionCommand("C" + _numRows); 322 bc.addActionListener(this); 323 } 324 } else { 325 bw.setEnabled(true); 326 bw.setActionCommand("W" + _numRows); 327 bw.addActionListener(this); 328 if (writeOnly) { 329 br.setEnabled(false); 330 bc.setEnabled(false); 331 } else { 332 br.setEnabled(true); 333 br.setActionCommand("R" + _numRows); 334 br.addActionListener(this); 335 bc.setEnabled(true); 336 bc.setActionCommand("C" + _numRows); 337 bc.addActionListener(this); 338 } 339 } 340 _numRows++; 341 fireTableDataChanged(); 342 } 343 // make sure readonly set true if required 344 CvValue cv = _cvAllMap.get(s); 345 if (readOnly) { 346 cv.setReadOnly(readOnly); 347 } 348 if (infoOnly) { 349 cv.setReadOnly(!infoOnly); 350 cv.setWriteOnly(!infoOnly); 351 cv.setInfoOnly(infoOnly); 352 } 353 if (writeOnly) { 354 cv.setWriteOnly(writeOnly); 355 } 356 } 357 358 public boolean decoderDirty() { 359 int len = _cvDisplayVector.size(); 360 for (int i = 0; i < len; i++) { 361 if (_cvDisplayVector.elementAt(i).getState() == AbstractValue.ValueState.EDITED) { 362 if (log.isDebugEnabled()) { 363 log.debug("CV decoder dirty due to {}", _cvDisplayVector.elementAt(i).number()); 364 } 365 return true; 366 } 367 } 368 return false; 369 } 370 371 /** 372 * Register a VariableValue in a common store mapping CV numbers to 373 * variable names. This is for use by e.g. a CVTable to show tooltips 374 * efficiently. 375 * @param cv specific CV number that the variable references 376 * @param variableName from the variable being defined 377 */ 378 public void registerCvToVariableMapping(String cv, String variableName) { 379 // is there already a Set for these? 380 if ( ! cvToVarMap.containsKey(cv)) { 381 // no, create one 382 cvToVarMap.put(cv, Collections.newSetFromMap(new HashMap<String, Boolean>())); 383 } 384 // add the String 385 cvToVarMap.get(cv).add(variableName); 386 } 387 388 public Set<String> getCvToVariableMapping(String cv) { return cvToVarMap.get(cv); } 389 390 private HashMap<String, Set<String>> cvToVarMap = new HashMap<>(); 391 392 public void dispose() { 393 if (log.isDebugEnabled()) { 394 log.debug("dispose"); 395 } 396 397 // remove buttons 398 for (int i = 0; i < _writeButtons.size(); i++) { 399 _writeButtons.elementAt(i).removeActionListener(this); 400 } 401 for (int i = 0; i < _readButtons.size(); i++) { 402 _readButtons.elementAt(i).removeActionListener(this); 403 } 404 for (int i = 0; i < _compareButtons.size(); i++) { 405 _compareButtons.elementAt(i).removeActionListener(this); 406 } 407 408 // remove CV listeners 409 for (int i = 0; i < _cvDisplayVector.size(); i++) { 410 _cvDisplayVector.elementAt(i).removePropertyChangeListener(this); 411 } 412 413 // null references, so that they can be gc'd even if this isn't. 414 cvToVarMap = null; 415 416 _cvDisplayVector.removeAllElements(); 417 _cvDisplayVector = null; 418 419 _writeButtons.removeAllElements(); 420 _writeButtons = null; 421 422 _readButtons.removeAllElements(); 423 _readButtons = null; 424 425 _compareButtons.removeAllElements(); 426 _compareButtons = null; 427 428 _cvAllMap.clear(); 429 _cvAllMap = null; 430 431 _status = null; 432 433 } 434 435 int holdsAddress() { 436 int shortAddr = getCvByNumber("1").getValue(); 437 int longAddr = ((getCvByNumber("17").getValue()-192)<<8)+getCvByNumber("18").getValue(); 438 int addr = holdsLongAddress() ? longAddr : shortAddr; 439 return addr; 440 } 441 442 boolean holdsLongAddress() { 443 return (getCvByNumber("29").getValue() & 0x20) != 0; 444 } 445 446 private final static Logger log = LoggerFactory.getLogger(CvTableModel.class); 447}