001package jmri.jmrix.loconet.swing.lncvprog; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.util.List; 006 007import javax.annotation.Nonnull; 008import javax.swing.table.AbstractTableModel; 009import javax.swing.table.TableColumn; 010import javax.swing.table.TableColumnModel; 011 012import jmri.InstanceManager; 013import jmri.Programmer; 014import jmri.jmrit.decoderdefn.DecoderFile; 015import jmri.jmrit.decoderdefn.DecoderIndexFile; 016import jmri.jmrit.roster.Roster; 017import jmri.jmrit.roster.RosterEntry; 018import jmri.jmrit.symbolicprog.tabbedframe.PaneOpsProgFrame; 019import jmri.jmrix.ProgrammingTool; 020import jmri.jmrix.loconet.LncvDevicesManager; 021import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 022import jmri.jmrix.loconet.uhlenbrock.LncvDevice; 023import jmri.util.swing.JmriJOptionPane; 024 025/** 026 * Table model for the programmed LNCV Modules table. 027 * See Sv2f Programing tool 028 * 029 * @author Egbert Broerse Copyright (C) 2020 030 */ 031public class LncvProgTableModel extends AbstractTableModel implements PropertyChangeListener, ProgrammingTool { 032 033 public static final int COUNT_COLUMN = 0; 034 public static final int ARTICLE_COLUMN = 1; 035 public static final int MODADDR_COLUMN = 2; 036 public static final int CV_COLUMN = 3; 037 public static final int VALUE_COLUMN = 4; 038 public static final int DEVICENAMECOLUMN = 5; 039 public static final int ROSTERENTRYCOLUMN = 6; 040 public static final int OPENPRGMRBUTTONCOLUMN = 7; 041 static public final int NUMCOLUMNS = 8; 042 private final LncvProgPane parent; 043 private final transient LocoNetSystemConnectionMemo memo; 044 protected Roster _roster; 045 protected LncvDevicesManager lncvdm; 046 047 LncvProgTableModel(LncvProgPane parent, @Nonnull LocoNetSystemConnectionMemo memo) { 048 this.parent = parent; 049 this.memo = memo; 050 lncvdm = memo.getLncvDevicesManager(); 051 _roster = Roster.getDefault(); 052 lncvdm.addPropertyChangeListener(this); 053 } 054 055 public void initTable(javax.swing.JTable lncvModulesTable) { 056 TableColumnModel assignmentColumnModel = lncvModulesTable.getColumnModel(); 057 TableColumn idColumn = assignmentColumnModel.getColumn(0); 058 idColumn.setMaxWidth(8); 059 } 060 061 @Override 062 public String getColumnName(int c) { 063 switch (c) { 064 case ARTICLE_COLUMN: 065 return Bundle.getMessage("HeadingArticle"); 066 case MODADDR_COLUMN: 067 return Bundle.getMessage("HeadingAddress"); 068 case CV_COLUMN: 069 return Bundle.getMessage("HeadingCvLastRead"); 070 case VALUE_COLUMN: 071 return Bundle.getMessage("HeadingValue"); 072 case DEVICENAMECOLUMN: 073 return Bundle.getMessage("HeadingDeviceModel"); 074 case ROSTERENTRYCOLUMN: 075 return Bundle.getMessage("HeadingDeviceId"); 076 case OPENPRGMRBUTTONCOLUMN: 077 return Bundle.getMessage("ButtonProgram"); 078 case COUNT_COLUMN: 079 default: 080 return "#"; 081 } 082 } 083 084 @Override 085 public Class<?> getColumnClass(int c) { 086 switch (c) { 087 case COUNT_COLUMN: 088 case ARTICLE_COLUMN: 089 case MODADDR_COLUMN: 090 case CV_COLUMN: 091 case VALUE_COLUMN: 092 return Integer.class; 093 case OPENPRGMRBUTTONCOLUMN: 094 return javax.swing.JButton.class; 095 case DEVICENAMECOLUMN: 096 case ROSTERENTRYCOLUMN: 097 default: 098 return String.class; 099 } 100 } 101 102 @Override 103 public boolean isCellEditable(int r, int c) { 104 return (c == OPENPRGMRBUTTONCOLUMN); 105 } 106 107 @Override 108 public int getColumnCount() { 109 return NUMCOLUMNS; 110 } 111 112 @Override 113 public int getRowCount() { 114 if (lncvdm == null) { 115 return 0; 116 } else { 117 return lncvdm.getDeviceCount(); 118 } 119 } 120 121 @Override 122 public Object getValueAt(int r, int c) { 123 LncvDevice dev = memo.getLncvDevicesManager().getDeviceList().getDevice(r); 124 try { 125 switch (c) { 126 case ARTICLE_COLUMN: 127 assert dev != null; 128 return dev.getProductID(); 129 case MODADDR_COLUMN: 130 assert dev != null; 131 return dev.getDestAddr(); 132 case CV_COLUMN: 133 assert dev != null; 134 return dev.getCvNum(); 135 case VALUE_COLUMN: 136 assert dev != null; 137 return dev.getCvValue(); 138 case DEVICENAMECOLUMN: 139 assert dev != null; 140 if (dev.getDeviceName().length() == 0) { // not yet filled in, look for a candidate 141 List<DecoderFile> l = 142 InstanceManager.getDefault( 143 DecoderIndexFile.class). 144 matchingDecoderList( 145 null, 146 null, 147 null, 148 null, 149 String.valueOf(dev.getProductID()), // a bit risky to check just 1 value 150 null, 151 null, 152 null, 153 null 154 ); 155 //log.debug("found {} possible decoder matches for LNCV device", l.size()); 156 String lastModelName = ""; 157 if (l.size() > 0) { 158 for (DecoderFile d : l) { 159 // we do not check for LNCV programmingMode support since we do not expect replies from non-LNCV devices 160 // (and there is currently no access to supported modes in the DecoderIndexFile) 161 if (d.getModel().equals("")) { 162 log.warn("Empty model(name) in decoderfile {}", d.getFileName()); 163 continue; 164 } 165 lastModelName = d.getModel(); 166 } 167 dev.setDevName(lastModelName); 168 dev.setDecoderFile(l.get(l.size() - 1)); 169 } 170 return lastModelName; 171 } 172 return dev.getDeviceName(); 173 case ROSTERENTRYCOLUMN: 174 assert dev != null; 175 return dev.getRosterName(); 176 case OPENPRGMRBUTTONCOLUMN: 177 assert dev != null; 178 if (dev.getDeviceName().length() != 0) { 179 if ((dev.getRosterName() != null) && (dev.getRosterName().length() == 0)) { 180 return Bundle.getMessage("ButtonCreateEntry"); 181 } 182 return Bundle.getMessage("ButtonProgram"); 183 } 184 return Bundle.getMessage("ButtonNoMatchInRoster"); 185 default: // column 1 186 return r + 1; 187 } 188 } catch (NullPointerException npe) { 189 log.warn("Caught NPE reading Module {}", r); 190 return ""; 191 } 192 } 193 194 @Override 195 public void setValueAt(Object value, int r, int c) { 196 if (getRowCount() < r + 1) { 197 // prevent update of a row that does not (yet) exist 198 return; 199 } 200 LncvDevice dev = memo.getLncvDevicesManager().getDeviceList().getDevice(r); 201 if (c == OPENPRGMRBUTTONCOLUMN) { 202 if (((String) getValueAt(r, c)).compareTo(Bundle.getMessage("ButtonCreateEntry")) == 0) { 203 createRosterEntry(dev); 204 if (dev.getRosterEntry() != null) { 205 setValueAt(dev.getRosterName(), r, c); 206 } else { 207 log.warn("Failed to connect RosterEntry to device {}", dev.getRosterName()); 208 } 209 } else if (((String) getValueAt(r, c)).compareTo(Bundle.getMessage("ButtonProgram")) == 0) { 210 openProgrammer(r); 211 } else if (((String) getValueAt(r, c)).compareTo(Bundle.getMessage("ButtonNoMatchInRoster")) == 0){ 212 // need to rebuild decoderIndex, tooltip? 213 warnRecreate(); 214 } 215 } else { 216 // no change, so do not fire a property change event 217 return; 218 } 219 if (getRowCount() >= 1) { 220 this.fireTableRowsUpdated(r, r); 221 } 222 } 223 224 private void openProgrammer(int r) { 225 LncvDevice dev = memo.getLncvDevicesManager().getDeviceList().getDevice(r); 226 227 LncvDevicesManager.ProgrammingResult result = lncvdm.prepareForSymbolicProgrammer(dev, this); 228 switch (result) { 229 case SUCCESS_PROGRAMMER_OPENED: 230 return; 231 case FAIL_NO_SUCH_DEVICE: 232 JmriJOptionPane.showMessageDialog(parent, 233 Bundle.getMessage("FAIL_NO_SUCH_DEVICE"), 234 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 235 return; 236 case FAIL_NO_APPROPRIATE_PROGRAMMER: 237 JmriJOptionPane.showMessageDialog(parent, 238 Bundle.getMessage("FAIL_NO_APPROPRIATE_PROGRAMMER"), 239 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 240 return; 241 case FAIL_NO_MATCHING_ROSTER_ENTRY: 242 JmriJOptionPane.showMessageDialog(parent, 243 Bundle.getMessage("FAIL_NO_MATCHING_ROSTER_ENTRY"), 244 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 245 return; 246 case FAIL_DESTINATION_ADDRESS_IS_ZERO: 247 JmriJOptionPane.showMessageDialog(parent, 248 Bundle.getMessage("FAIL_DESTINATION_ADDRESS_IS_ZERO"), 249 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 250 return; 251 case FAIL_MULTIPLE_DEVICES_SAME_DESTINATION_ADDRESS: 252 JmriJOptionPane.showMessageDialog(parent, 253 Bundle.getMessage("FAIL_MULTIPLE_DEVICES_SAME_DESTINATION_ADDRESS", dev.getDestAddr()), 254 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 255 return; 256 case FAIL_NO_ADDRESSED_PROGRAMMER: 257 JmriJOptionPane.showMessageDialog(parent, 258 Bundle.getMessage("FAIL_NO_ADDRESSED_PROGRAMMER"), 259 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 260 return; 261 case FAIL_NO_LNCV_PROGRAMMER: 262 JmriJOptionPane.showMessageDialog(parent, 263 Bundle.getMessage("FAIL_NO_LNCV_PROGRAMMER"), 264 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 265 return; 266 default: 267 JmriJOptionPane.showMessageDialog(parent, 268 Bundle.getMessage("FAIL_UNKNOWN"), 269 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 270 } 271 } 272 273 /** 274 * {@inheritDoc} 275 */ 276 @Override 277 public void openPaneOpsProgFrame(RosterEntry re, String name, 278 String programmerFile, Programmer p) { 279 // would be better if this was a new task on the GUI thread... 280 log.debug("attempting to open programmer, re={}, name={}, programmerFile={}, programmer={}", 281 re, name, programmerFile, p); 282 283 DecoderFile decoderFile = InstanceManager.getDefault(DecoderIndexFile.class).fileFromTitle(re.getDecoderModel()); 284 285 PaneOpsProgFrame progFrame = 286 new PaneOpsProgFrame(decoderFile, re, name, programmerFile, p); 287 288 progFrame.pack(); 289 progFrame.setVisible(true); 290 } 291 292 private void createRosterEntry(LncvDevice dev) { 293 if (dev.getDestAddr() == 0) { 294 JmriJOptionPane.showMessageDialog(parent, 295 Bundle.getMessage("FAIL_ADD_ENTRY_0"), 296 Bundle.getMessage("ButtonCreateEntry"), JmriJOptionPane.ERROR_MESSAGE); 297 } else { 298 String s = null; 299 while (s == null) { 300 s = JmriJOptionPane.showInputDialog(parent, 301 Bundle.getMessage("DialogEnterEntryName"), 302 Bundle.getMessage("EnterEntryNameTitle"),JmriJOptionPane.QUESTION_MESSAGE); 303 if (s == null) { 304 // Cancel button hit 305 return; 306 } 307 } 308 309 RosterEntry re = new RosterEntry(dev.getDecoderFile().getFileName()); 310 re.setDccAddress(Integer.toString(dev.getDestAddr())); 311 re.setDecoderModel(dev.getDecoderFile().getModel()); 312 re.setProductID(Integer.toString(dev.getProductID())); 313 re.setId(s); 314 _roster.addEntry(re); 315 dev.setRosterEntry(re); 316 } 317 } 318 319 private void warnRecreate() { 320 // show dialog to inform and allow rebuilding index 321 Object[] dialogBoxButtonOptions = { 322 Bundle.getMessage("ButtonRecreateIndex"), 323 Bundle.getMessage("ButtonCancel")}; 324 int userReply = JmriJOptionPane.showOptionDialog(parent, 325 Bundle.getMessage("DialogWarnRecreate"), 326 Bundle.getMessage("TitleOpenRosterEntry"), 327 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, 328 null, dialogBoxButtonOptions, dialogBoxButtonOptions[0]); 329 if (userReply == 0) { // array position 0 330 DecoderIndexFile.forceCreationOfNewIndex(false); // faster 331 } 332 } 333 334 /* 335 * Process the "property change" events from LncvDevicesManager. 336 * 337 * @param evt event 338 */ 339 @Override 340 public void propertyChange(PropertyChangeEvent evt) { 341 // these messages can arrive without a complete 342 // GUI, in which case we just ignore them 343 //String eventName = evt.getPropertyName(); 344 /* always use fireTableDataChanged() because it does not always 345 resize columns to "preferred" widths! 346 This may slow things down, but that is a small price to pay! 347 */ 348 fireTableDataChanged(); 349 } 350 351 public void dispose() { 352 if ((memo != null) && (memo.getLncvDevicesManager() != null)) { 353 memo.getLncvDevicesManager().removePropertyChangeListener(this); 354 } 355 } 356 357 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LncvProgTableModel.class); 358 359}