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.ArrayList; 008import java.util.List; 009import javax.swing.JButton; 010import javax.swing.JLabel; 011import javax.swing.table.AbstractTableModel; 012import jmri.Programmer; 013import jmri.ProgrammingMode; 014import jmri.util.jdom.LocaleSelector; 015import org.jdom2.Element; 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019/** 020 * Holds a table of the extra menu items available for a particular decoder. 021 * 022 * @see ResetTableModel 023 * 024 * @author Howard G. Penny Copyright (C) 2005 025 * @author Bob Jacobsen Copyright (C) 2021 026 */ 027public class ExtraMenuTableModel extends AbstractTableModel implements ActionListener, PropertyChangeListener { 028 029 private String headers[] = {"Label", "Name", 030 "Value", 031 "Write", "State"}; 032 033 private List<CvValue> rowVector = new ArrayList<>(); // vector of Reset items 034 private List<String> labelVector = new ArrayList<>(); // vector of related labels 035 private List<List<String>> modeVector = new ArrayList<>(); // vector of related modes 036 037 private List<JButton> _writeButtons = new ArrayList<>(); 038 039 private JLabel _status = null; 040 private Programmer mProgrammer; 041 042 String name = "<default>"; // User visible menu name 043 044 public String getName() { return name; } 045 public void setName(String n) { this.name = n; } 046 047 public ExtraMenuTableModel(JLabel status, Programmer pProgrammer) { 048 super(); 049 050 mProgrammer = pProgrammer; 051 // save a place for notification 052 _status = status; 053 } 054 055 @Override 056 public String toString() { 057 return "Element id: "+getTopLevelElementName()+" name: "+name+": "+rowVector.size()+" rows"; 058 } 059 060 public void setProgrammer(Programmer p) { 061 mProgrammer = p; 062 063 // pass on to all contained CVs 064 rowVector.forEach((cv) -> { 065 cv.setProgrammer(p); 066 }); 067 } 068 069 private boolean hasOpsModeFlag = false; 070 071 protected void flagIfOpsMode(String mode) { 072 log.trace(" flagIfOpsMode {}", mode); 073 if (mode.contains("OPS")) { 074 hasOpsModeFlag = true; 075 } 076 } 077 078 public boolean hasOpsModeReset() { 079 return hasOpsModeFlag; 080 } 081 082 @Override 083 public int getRowCount() { 084 return rowVector.size(); 085 } 086 087 @Override 088 public int getColumnCount() { 089 return headers.length; 090 } 091 092 @Override 093 public Object getValueAt(int row, int col) { 094 // log.debug("getValueAt "+row+" "+col); 095 // some error checking 096 if (row >= rowVector.size()) { 097 log.debug("row greater than row vector"); 098 return "Error"; 099 } 100 CvValue cv = rowVector.get(row); 101 if (cv == null) { 102 log.debug("cv is null!"); 103 return "Error CV"; 104 } 105 switch (headers[col]) { 106 case "Label": 107 return "" + labelVector.get(row); 108 case "Name": 109 return "" + cv.cvName(); 110 case "Value": 111 return "" + cv.getValue(); 112 case "Write": 113 return _writeButtons.get(row); 114 case "State": 115 AbstractValue.ValueState state = cv.getState(); 116 switch (state) { 117 case UNKNOWN: 118 return "Unknown"; 119 case READ: 120 return "Read"; 121 case EDITED: 122 return "Edited"; 123 case STORED: 124 return "Stored"; 125 case FROMFILE: 126 return "From file"; 127 default: 128 return "inconsistent"; 129 } 130 default: 131 return "hmmm ... missed it"; 132 } 133 } 134 135 public void setRow(int row, Element e, Element p, String model) { 136 decoderModel = model; // Save for use elsewhere 137 String label = LocaleSelector.getAttribute(e, "label"); // Note the name variable is actually the label attribute 138 log.debug("Starting to setRow \"{}\"", label); 139 String cv = e.getAttribute("CV").getValue(); 140 int cvVal = Integer.parseInt(e.getAttribute("default").getValue()); 141 142 log.debug(" CV \"{}\" value {}", cv, cvVal); 143 144 CvValue resetCV = new CvValue(cv, mProgrammer); 145 resetCV.addPropertyChangeListener(this); 146 resetCV.setValue(cvVal); 147 resetCV.setWriteOnly(true); 148 resetCV.setState(AbstractValue.ValueState.STORED); 149 rowVector.add(resetCV); 150 labelVector.add(label); 151 modeVector.add(getResetModeList(e, p)); 152 } 153 154 protected List<String> getResetModeList(Element e, Element p) { 155 List<Element> elementList = new ArrayList<>(); 156 List<String> modeList = new ArrayList<>(); 157 List<Element> elementModes; 158 String mode; 159 boolean resetsModeFound = false; 160 161 elementList.add(p); 162 elementList.add(e); 163 164 for (Element ep : elementList) { 165 try { 166 mode = ep.getAttribute("mode").getValue(); 167 if (ep.getName().equals(getTopLevelElementName())) { 168 resetsModeFound = true; 169 } else if (resetsModeFound) { 170 modeList.clear(); 171 resetsModeFound = false; 172 } 173 modeList.add(mode); 174 flagIfOpsMode(mode); 175 } catch (NullPointerException ex) { 176 // ignore as expected result if there is no attribute mode 177 } 178 179 try { 180 elementModes = ep.getChildren("mode"); 181 for (Element s : elementModes) { 182 if (ep.getName().equals(getTopLevelElementName())) { 183 resetsModeFound = true; 184 } else if (resetsModeFound) { 185 modeList.clear(); 186 resetsModeFound = false; 187 } 188 modeList.add(s.getText()); 189 flagIfOpsMode(s.getText()); 190 } 191 } catch (NullPointerException ex) { 192 // ignore as expected result if there is no attribute mode 193 } 194 } 195 196 return modeList; 197 } 198 199 /** 200 * Name of the XML element for the collection of extra menu items 201 * @return element name for top level menu item 202 */ 203 public String getTopLevelElementName() { 204 return "resets"; 205 } 206 207 /** 208 * Name of the XML element for individual menu items 209 * @return element name for individual menu item 210 */ 211 public String getIndividualElementName() { 212 return "factReset"; 213 } 214 215 private ProgrammingMode savedMode; 216 private String decoderModel; 217 218 protected void performReset(int row) { 219 savedMode = mProgrammer.getMode(); // In case we need to change modes 220 if (modeVector.get(row) != null) { 221 List<ProgrammingMode> modes = mProgrammer.getSupportedModes(); 222 List<String> validModes = modeVector.get(row); 223 224 StringBuilder programmerModeListBuffer = new StringBuilder(""); 225 modes.forEach((m) -> { 226 programmerModeListBuffer.append(",").append(m.toString()); 227 }); 228 String programmerModeList = programmerModeListBuffer.toString(); 229 if (programmerModeList.length() <= 1) { 230 programmerModeList = ""; // NOI18N 231 } else if (programmerModeList.startsWith(",")) { 232 programmerModeList = programmerModeList.substring(1); 233 } 234 235 StringBuilder resetModeBuilder = new StringBuilder(""); 236 validModes.forEach((mode) -> { 237 resetModeBuilder.append(",").append(new ProgrammingMode(mode).toString()); 238 }); 239 String resetModeList = resetModeBuilder.toString(); 240 if (resetModeList.length() <= 1) { 241 resetModeList = ""; // NOI18N 242 } else if (resetModeList.startsWith(",")) { 243 resetModeList = resetModeList.substring(1); 244 } 245 246 if (resetModeList.length() > 0) { 247 boolean modeFound = false; 248 search: 249 for (ProgrammingMode m : modes) { 250 for (String mode : validModes) { 251 if (mode.equals(m.getStandardName())) { 252 mProgrammer.setMode(m); 253 modeFound = true; 254 break search; 255 } 256 } 257 } 258 259 if (mProgrammer.getMode().getStandardName().contains("OPS")) { 260 if (!opsResetOk()) { 261 return; 262 } 263 } 264 265 if (!modeFound) { 266 if (!badModeOk((savedMode.toString()), resetModeList, programmerModeList)) { 267 return; 268 } 269 log.warn("{} for {} was attempted in {} mode.", labelVector.get(row), decoderModel, savedMode); 270 log.warn("Recommended mode(s) were \"{}\" but available modes were \"{}\"", resetModeList, programmerModeList); 271 } 272 } 273 } 274 CvValue cv = rowVector.get(row); 275 log.debug("performReset: {}", cv); 276 _progState = WRITING_CV; 277 cv.write(_status); 278 } 279 280 @Override 281 public void actionPerformed(ActionEvent e) { 282 log.debug("action command: {}", e.getActionCommand()); 283 char b = e.getActionCommand().charAt(0); 284 int row = Integer.parseInt(e.getActionCommand().substring(1)); 285 log.debug("event on {} row {}", b, row); 286 if (b == 'W') { 287 // write command 288 performReset(row); 289 } 290 } 291 292 private int _progState = 0; 293 private static final int IDLE = 0; 294 private static final int WRITING_CV = 3; 295 296 @Override 297 public void propertyChange(PropertyChangeEvent e) { 298 299 log.debug("Property changed: {}", e.getPropertyName()); 300 // notification from Indexed CV; check for Value being changed 301 if (e.getPropertyName().equals("Busy") && ((Boolean) e.getNewValue()).equals(Boolean.FALSE)) { 302 // busy transitions drive the state 303 switch (_progState) { 304 case IDLE: // no, just an Indexed CV update 305 log.debug("Busy goes false with state IDLE"); 306 return; 307 case WRITING_CV: // now done with the write request 308 log.debug("Finished writing the CV"); 309 mProgrammer.setMode(savedMode); 310 _progState = IDLE; 311 return; 312 default: // unexpected! 313 log.error("Unexpected state found: {}", _progState); 314 mProgrammer.setMode(savedMode); 315 _progState = IDLE; 316 } 317 } 318 } 319 320 /** 321 * Can provide some mechanism to prompt for user for one last chance to 322 * change his/her mind 323 * @param currentMode current programming mode 324 * @param resetModes representation of reset modes available 325 * @param availableModes representation of available modes 326 * @return true if user says to continue 327 */ 328 boolean badModeOk(String currentMode, String resetModes, String availableModes) { 329 return true; 330 } 331 332 /** 333 * Can provide some mechanism to prompt for user for one last chance to 334 * change his/her mind 335 * 336 * @return true if user says to continue 337 */ 338 boolean opsResetOk() { 339 return true; 340 } 341 342 public void dispose() { 343 log.debug("dispose"); 344 345 // remove buttons 346 for (int i = 0; i < _writeButtons.size(); i++) { 347 _writeButtons.get(i).removeActionListener(this); 348 } 349 350 _writeButtons.clear(); 351 _writeButtons = null; 352 353 // remove variables listeners 354 for (int i = 0; i < rowVector.size(); i++) { 355 CvValue cv = rowVector.get(i); 356 cv.dispose(); 357 } 358 rowVector.clear(); 359 rowVector = null; 360 361 labelVector.clear(); 362 labelVector = null; 363 364 modeVector.clear(); 365 modeVector = null; 366 367 headers = null; 368 369 _status = null; 370 } 371 372 // initialize logging 373 private final static Logger log = LoggerFactory.getLogger(ExtraMenuTableModel.class); 374}