001package jmri.jmrix.loconet.swing.lnsv1prog; 002 003import jmri.InstanceManager; 004import jmri.Programmer; 005import jmri.jmrit.decoderdefn.DecoderFile; 006import jmri.jmrit.decoderdefn.DecoderIndexFile; 007import jmri.jmrit.roster.Roster; 008import jmri.jmrit.roster.RosterEntry; 009import jmri.jmrit.symbolicprog.tabbedframe.PaneOpsProgFrame; 010import jmri.jmrix.ProgrammingTool; 011import jmri.jmrix.loconet.Lnsv1DevicesManager; 012import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 013import jmri.jmrix.loconet.lnsvf1.Lnsv1Device; 014import jmri.util.swing.JmriJOptionPane; 015 016import javax.annotation.Nonnull; 017import javax.swing.table.AbstractTableModel; 018import javax.swing.table.TableColumn; 019import javax.swing.table.TableColumnModel; 020import java.beans.PropertyChangeEvent; 021import java.beans.PropertyChangeListener; 022 023/** 024 * Table model for the programmed LNSV1 Modules table. 025 * See Svf1 Programing tool 026 * 027 * @author Egbert Broerse Copyright (C) 2020, 2025 028 */ 029public class Lnsv1ProgTableModel extends AbstractTableModel implements PropertyChangeListener, ProgrammingTool { 030 031 public static final int COUNT_COLUMN = 0; 032 public static final int MODADDR_COLUMN = 1; 033 public static final int MODADDRSPLIT_COLUMN = 2; 034 public static final int VERSION_COLUMN = 3; 035 public static final int CV_COLUMN = 4; 036 public static final int VALUE_COLUMN = 5; 037 public static final int ROSTERENTRY_COLUMN = 6; 038 public static final int ROSTERSV1MODE_COLUMN = 7; 039 public static final int ROSTERNAME_COLUMN = 8; 040 public static final int OPENPRGMRBUTTON_COLUMN = 9; 041 static public final int NUMCOLUMNS = 10; 042 private final Lnsv1ProgPane parent; 043 private final transient LocoNetSystemConnectionMemo memo; 044 protected Roster _roster; 045 protected Lnsv1DevicesManager lnsv1dm; 046 047 Lnsv1ProgTableModel(Lnsv1ProgPane parent, @Nonnull LocoNetSystemConnectionMemo memo) { 048 this.parent = parent; 049 this.memo = memo; 050 lnsv1dm = memo.getLnsv1DevicesManager(); 051 _roster = Roster.getDefault(); 052 lnsv1dm.addPropertyChangeListener(this); 053 } 054 055 public void initTable(javax.swing.JTable lnsv1ModulesTable) { 056 TableColumnModel assignmentColumnModel = lnsv1ModulesTable.getColumnModel(); 057 TableColumn idColumn = assignmentColumnModel.getColumn(0); 058 idColumn.setMaxWidth(3); 059 } 060 061 @Override 062 public String getColumnName(int c) { 063 switch (c) { 064 case MODADDR_COLUMN: 065 return Bundle.getMessage("HeadingDccAddress"); 066 case MODADDRSPLIT_COLUMN: 067 return Bundle.getMessage("HeadingAddressSplit"); 068 case VERSION_COLUMN: 069 return Bundle.getMessage("HeadingVersion"); 070 case CV_COLUMN: 071 return Bundle.getMessage("HeadingCvLastRead"); 072 case VALUE_COLUMN: 073 return Bundle.getMessage("HeadingValue"); 074 case ROSTERENTRY_COLUMN: 075 return Bundle.getMessage("HeadingDeviceId"); 076 case ROSTERNAME_COLUMN: 077 return Bundle.getMessage("HeadingDeviceModel"); 078 case ROSTERSV1MODE_COLUMN: 079 return Bundle.getMessage("HeadingIsSv1"); 080 case OPENPRGMRBUTTON_COLUMN: 081 return Bundle.getMessage("ButtonProgram"); 082 case COUNT_COLUMN: 083 default: 084 return "#"; 085 } 086 } 087 088 @Override 089 public Class<?> getColumnClass(int c) { 090 switch (c) { 091 case MODADDR_COLUMN: 092 case COUNT_COLUMN: 093 case VERSION_COLUMN: 094 case CV_COLUMN: 095 case VALUE_COLUMN: 096 return Integer.class; 097 case OPENPRGMRBUTTON_COLUMN: 098 return javax.swing.JButton.class; 099 case ROSTERSV1MODE_COLUMN: 100 return Boolean.class; 101 case MODADDRSPLIT_COLUMN: 102 case ROSTERNAME_COLUMN: 103 case ROSTERENTRY_COLUMN: 104 default: 105 return String.class; 106 } 107 } 108 109 @Override 110 public boolean isCellEditable(int r, int c) { 111 return (c == OPENPRGMRBUTTON_COLUMN); 112 } 113 114 @Override 115 public int getColumnCount() { 116 return NUMCOLUMNS; 117 } 118 119 @Override 120 public int getRowCount() { 121 if (lnsv1dm == null) { 122 return 0; 123 } else { 124 return lnsv1dm.getDeviceCount(); 125 } 126 } 127 128 @Override 129 public Object getValueAt(int r, int c) { 130 Lnsv1Device dev = memo.getLnsv1DevicesManager().getDeviceList().getDevice(r); 131 try { 132 switch (c) { 133 case MODADDR_COLUMN: 134 assert dev != null; 135 return dev.getDestAddr(); 136 case MODADDRSPLIT_COLUMN: 137 assert dev != null; 138 return dev.getDestAddrLow() + "/" + dev.getDestAddrHigh(); 139 case VERSION_COLUMN: 140 assert dev != null; 141 return dev.getSwVersion(); 142 case CV_COLUMN: 143 assert dev != null; 144 return dev.getCvNum(); 145 case VALUE_COLUMN: 146 assert dev != null; 147 return dev.getCvValue(); 148 case ROSTERENTRY_COLUMN: 149 assert dev != null; 150 return dev.getRosterEntry().getId(); 151 case ROSTERSV1MODE_COLUMN: 152 boolean isLnsv1 = false; 153 if (dev != null && dev.getDecoderFile() != null) { 154 isLnsv1 = dev.getDecoderFile().isProgrammingMode("LOCONETSV1MODE"); 155 // can't access LnProgrammerManager.LOCONETSV1MODE constant 156 } 157 return isLnsv1; 158 case ROSTERNAME_COLUMN: 159 assert dev != null; 160 if (dev.getRosterEntry() != null) { 161 return dev.getRosterEntry().getDecoderModel(); 162 } else { 163 return ""; 164 } 165 case OPENPRGMRBUTTON_COLUMN: 166 if (dev != null && !dev.getRosterName().isEmpty()) { 167 if (dev.getDecoderFile().isProgrammingMode("LOCONETSV1MODE")) { 168 return Bundle.getMessage("ButtonProgram"); 169 } else { 170 return Bundle.getMessage("ButtonWrongMode"); 171 } 172 } 173 return Bundle.getMessage("ButtonNoMatchInRoster"); 174 default: // column 0 175 return r + 1; 176 } 177 } catch (NullPointerException npe) { 178 log.warn("No match for Module {}, c{}", r, c); 179 return ""; 180 } 181 } 182 183 @Override 184 public void setValueAt(Object value, int r, int c) { 185 if (getRowCount() < r + 1) { 186 // prevent update of a row that does not (yet) exist 187 return; 188 } 189 if (c == OPENPRGMRBUTTON_COLUMN) { 190 if (((String) getValueAt(r, c)).compareTo(Bundle.getMessage("ButtonProgram")) == 0) { 191 openProgrammer(r); 192 } else if (((String) getValueAt(r, c)).compareTo(Bundle.getMessage("ButtonWrongMode")) == 0) { 193 infoNotForLnsv1(getValueAt(r, 1).toString()); // TODO once we check for LNSV1 progMode this can be removed 194 } else if (((String) getValueAt(r, c)).compareTo(Bundle.getMessage("ButtonNoMatchInRoster")) == 0){ 195 // no match, info add roster entry 196 infoNoMatch(getValueAt(r, 1).toString()); 197 } 198 } else { 199 // no change, so do not fire a property change event 200 return; 201 } 202 if (getRowCount() >= 1) { 203 this.fireTableRowsUpdated(r, r); 204 } 205 } 206 207 private void openProgrammer(int r) { 208 Lnsv1Device dev = memo.getLnsv1DevicesManager().getDeviceList().getDevice(r); 209 210 Lnsv1DevicesManager.ProgrammingResult result = lnsv1dm.prepareForSymbolicProgrammer(dev, this); 211 switch (result) { 212 case SUCCESS_PROGRAMMER_OPENED: 213 return; 214 case FAIL_NO_SUCH_DEVICE: 215 JmriJOptionPane.showMessageDialog(parent, 216 Bundle.getMessage("FAIL_NO_SUCH_DEVICE"), 217 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 218 return; 219 case FAIL_NO_APPROPRIATE_PROGRAMMER: 220 JmriJOptionPane.showMessageDialog(parent, 221 Bundle.getMessage("FAIL_NO_APPROPRIATE_PROGRAMMER"), 222 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 223 return; 224 case FAIL_NO_MATCHING_ROSTER_ENTRY: 225 JmriJOptionPane.showMessageDialog(parent, 226 Bundle.getMessage("FAIL_NO_MATCHING_ROSTER_ENTRY"), 227 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 228 return; 229 case FAIL_DESTINATION_ADDRESS_IS_ZERO: 230 JmriJOptionPane.showMessageDialog(parent, 231 Bundle.getMessage("FAIL_DESTINATION_ADDRESS_IS_ZERO"), 232 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 233 return; 234 case FAIL_MULTIPLE_DEVICES_SAME_DESTINATION_ADDRESS: 235 JmriJOptionPane.showMessageDialog(parent, 236 Bundle.getMessage("FAIL_MULTIPLE_DEVICES_SAME_DESTINATION_ADDRESS", dev.getDestAddr()), 237 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 238 return; 239 case FAIL_NO_ADDRESSED_PROGRAMMER: 240 JmriJOptionPane.showMessageDialog(parent, 241 Bundle.getMessage("FAIL_NO_ADDRESSED_PROGRAMMER"), 242 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 243 return; 244 case FAIL_NO_LNSV1_PROGRAMMER: 245 JmriJOptionPane.showMessageDialog(parent, 246 Bundle.getMessage("FAIL_NO_LNSV1_PROGRAMMER"), 247 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 248 return; 249 default: 250 JmriJOptionPane.showMessageDialog(parent, 251 Bundle.getMessage("FAIL_UNKNOWN"), 252 Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE); 253 } 254 } 255 256 /** 257 * {@inheritDoc} 258 */ 259 @Override 260 public void openPaneOpsProgFrame(RosterEntry re, String name, 261 String programmerFile, Programmer p) { 262 // would be better if this was a new task on the GUI thread... 263 log.debug("attempting to open programmer, re={}, name={}, programmerFile={}, programmer={}", 264 re, name, programmerFile, p); 265 266 DecoderFile decoderFile = InstanceManager.getDefault(DecoderIndexFile.class).fileFromTitle(re.getDecoderModel()); 267 268 PaneOpsProgFrame progFrame = 269 new PaneOpsProgFrame(decoderFile, re, name, programmerFile, p); 270 271 progFrame.pack(); 272 progFrame.setVisible(true); 273 } 274 275 /** 276 * Show dialog to instruct to add a roster entry supporting LNSV1 prog mode. 277 */ 278 private void infoNoMatch(String address) { 279 //log.debug("address = {}", address); 280 Object[] dialogBoxButtonOptions = { 281 Bundle.getMessage("ButtonOK")}; 282 JmriJOptionPane.showOptionDialog(parent, 283 Bundle.getMessage("DialogInfoNoRosterMatch", address), // not displaying addr? 284 Bundle.getMessage("TitleOpenRosterEntry"), 285 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.INFORMATION_MESSAGE, 286 null, dialogBoxButtonOptions, dialogBoxButtonOptions[0]); 287 } 288 289 /** 290 * Show dialog to inform that address matched decoder doesn't support LNSV1 mode. 291 */ 292 private void infoNotForLnsv1(String address) { 293 Object[] dialogBoxButtonOptions = { 294 Bundle.getMessage("ButtonOK")}; 295 JmriJOptionPane.showOptionDialog(parent, 296 Bundle.getMessage("DialogInfoMatchNotX", address, "LNSV1"), 297 Bundle.getMessage("TitleOpenRosterEntry"), 298 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.INFORMATION_MESSAGE, 299 null, dialogBoxButtonOptions, dialogBoxButtonOptions[0]); 300 } 301 302 /* 303 * Process the "property change" events from Lnsv1DevicesManager. 304 * 305 * @param evt event 306 */ 307 @Override 308 public void propertyChange(PropertyChangeEvent evt) { 309 // these messages can arrive without a complete 310 // GUI, in which case we just ignore them 311 //String eventName = evt.getPropertyName(); 312 /* always use fireTableDataChanged() because it does not always 313 resize columns to "preferred" widths! 314 This may slow things down, but that is a small price to pay! 315 */ 316 fireTableDataChanged(); 317 } 318 319 public void dispose() { 320 if ((memo != null) && (memo.getLnsv1DevicesManager() != null)) { 321 memo.getLnsv1DevicesManager().removePropertyChangeListener(this); 322 } 323 } 324 325 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Lnsv1ProgTableModel.class); 326 327}