001package jmri.jmrix.openlcb.swing.lccpro; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005 006import javax.swing.Icon; 007import javax.swing.ImageIcon; 008import javax.swing.JButton; 009import javax.swing.JLabel; 010import javax.swing.table.DefaultTableModel; 011 012import jmri.jmrix.can.CanSystemConnectionMemo; 013 014import org.openlcb.*; 015 016/** 017 * Table data model for display of LCC node values. 018 * <p> 019 * Any desired ordering, etc, is handled outside this class. 020 * 021 * @author Bob Jacobsen Copyright (C) 2009, 2010, 2024 022 * @since 5.11.1 023 */ 024public class LccProTableModel extends DefaultTableModel implements PropertyChangeListener { 025 026 static final int NAMECOL = 0; 027 public static final int IDCOL = 1; 028 static final int MFGCOL = 2; 029 static final int MODELCOL = 3; 030 static final int SVERSIONCOL = 4; 031 public static final int CONFIGURECOL = 5; 032 public static final int UPGRADECOL = 6; 033 public static final int BACKUPCOL = 7; 034 public static final int DESCRIPTIONCOL = 8; 035 public static final int NUMCOL = DESCRIPTIONCOL + 1; 036 037 CanSystemConnectionMemo memo; 038 MimicNodeStore nodestore; 039 040 public LccProTableModel(CanSystemConnectionMemo memo) { 041 this.memo = memo; 042 this.nodestore = memo.get(MimicNodeStore.class); 043 log.trace("Found nodestore {}", nodestore); 044 nodestore.addPropertyChangeListener(this); 045 nodestore.refresh(); 046 } 047 048 049 @Override 050 public void propertyChange(PropertyChangeEvent e) { 051 // set this up to fireTableDataChanged() when a new node appears 052 log.trace("received {}", e); 053 // SNIP might take a bit, so fire slightly later 054 jmri.util.ThreadingUtil.runOnGUIDelayed( ()->{ 055 fireTableDataChanged(); 056 }, 250); 057 jmri.util.ThreadingUtil.runOnGUIDelayed( ()->{ 058 fireTableDataChanged(); 059 }, 1000); 060 } 061 062 @Override 063 public int getRowCount() { 064 if (nodestore == null) { 065 log.trace("Did not expect null nodestore, except in initialization before ctor runs"); 066 return 1; 067 } 068 if (nodestore.getNodeMemos() == null) { 069 log.debug("Did not expect missing node memos"); 070 return 0; 071 } 072 return nodestore.getNodeMemos().size(); 073 } 074 075 @Override 076 public int getColumnCount() { 077 return NUMCOL; 078 } 079 080 @Override 081 public String getColumnName(int col) { 082 switch (col) { 083 case NAMECOL: 084 return Bundle.getMessage("FieldName"); 085 case IDCOL: 086 return Bundle.getMessage("FieldID"); 087 case MFGCOL: 088 return Bundle.getMessage("FieldMfg"); 089 case MODELCOL: 090 return Bundle.getMessage("FieldModel"); 091 case SVERSIONCOL: 092 return Bundle.getMessage("FieldSVersion"); 093 case CONFIGURECOL: 094 return Bundle.getMessage("FieldConfig"); 095 case UPGRADECOL: 096 return Bundle.getMessage("FieldUpgrade"); 097 case BACKUPCOL: 098 return Bundle.getMessage("FieldBackup"); 099 case DESCRIPTIONCOL: 100 return Bundle.getMessage("FieldDescription"); 101 default: 102 return "<unexpected column number>"; 103 } 104 } 105 106 @Override 107 public Class<?> getColumnClass(int col) { 108 switch (col) { 109 case CONFIGURECOL: 110 case UPGRADECOL: 111 case BACKUPCOL: 112 return JButton.class; 113 default: 114 return String.class; 115 } 116 } 117 118 /** 119 * {@inheritDoc} 120 * <p> 121 * Note that the table can be set to be non-editable when constructed, in 122 * which case this always returns false. 123 * 124 * @return true if cell is editable 125 */ 126 @Override 127 public boolean isCellEditable(int row, int col) { 128 switch (col) { 129 case CONFIGURECOL: 130 case UPGRADECOL: 131 case BACKUPCOL: 132 return true; 133 default: 134 return false; 135 } 136 } 137 138 /** 139 * {@inheritDoc} 140 * 141 * Provides an empty string for a column if the model returns null for that 142 * value. 143 */ 144 @Override 145 public Object getValueAt(int row, int col) { 146 log.trace("getValue({}, {})", row, col); 147 148 var memoArray = nodestore.getNodeMemos().toArray(new MimicNodeStore.NodeMemo[0]); 149 if (row >= memoArray.length) return ""; // sometimes happens at startup 150 var nodememo = memoArray[row]; 151 if (nodememo == null) return "<invalid node memo>"; 152 var snip = nodememo.getSimpleNodeIdent(); 153 if (snip == null) return "<snip info not yet availble>"; 154 var pip = nodememo.getProtocolIdentification(); 155 156 switch (col) { 157 case NAMECOL: 158 return snip.getUserName(); 159 case IDCOL: 160 return nodememo.getNodeID().toString(); 161 case MFGCOL: 162 return snip.getMfgName(); 163 case MODELCOL: 164 return snip.getModelName(); 165 case SVERSIONCOL: 166 return snip.getSoftwareVersion(); 167 case CONFIGURECOL: 168 if (pip.hasProtocol(ProtocolIdentification.Protocol.ConfigurationDescription)) { 169 return Bundle.getMessage("FieldConfig"); 170 } else { 171 return null; 172 } 173 case UPGRADECOL: 174 if (pip.hasProtocol(ProtocolIdentification.Protocol.FirmwareUpgrade)) { 175 return Bundle.getMessage("FieldUpgrade"); 176 } else { 177 return null; 178 } 179 case BACKUPCOL: 180 if (pip.hasProtocol(ProtocolIdentification.Protocol.ConfigurationDescription)) { 181 return Bundle.getMessage("FieldBackup"); 182 } else { 183 return null; 184 } 185 case DESCRIPTIONCOL: 186 return snip.getUserDesc(); 187 default: 188 return "<unexpected column number>"; 189 } 190 } 191 192 @Override 193 public void setValueAt(Object value, int row, int col) { 194 log.trace("getValue({}, {})", row, col); 195 MimicNodeStore.NodeMemo nodememo = nodestore.getNodeMemos().toArray(new MimicNodeStore.NodeMemo[0])[row]; 196 if (nodememo == null) { 197 log.error("Button pushed but no corresponding node for row {}", row); 198 return; 199 } 200 var pip = nodememo.getProtocolIdentification(); 201 202 switch (col) { 203 case CONFIGURECOL: 204 if (pip.hasProtocol(ProtocolIdentification.Protocol.ConfigurationDescription)) { 205 var actions = new jmri.jmrix.openlcb.swing.ClientActions(memo.get(org.openlcb.OlcbInterface.class), memo); 206 var node = nodememo.getNodeID(); 207 var description = jmri.jmrix.openlcb.swing.networktree.NetworkTreePane.augmentedNodeName(nodememo); 208 actions.openCdiWindow(node, description); 209 // We want the table to retain focus while the CDI loads 210 // This also removes the selection from this cell, so that cmd-` 211 // no longer repeats the action of pressing the button 212 forceFocus(); 213 } 214 break; 215 case UPGRADECOL: 216 if (pip.hasProtocol(ProtocolIdentification.Protocol.FirmwareUpgrade)) { 217 var node = nodememo.getNodeID(); 218 var action = new jmri.jmrix.openlcb.swing.downloader.LoaderPane.Default(node); 219 action.actionPerformed(null); 220 } 221 break; 222 case BACKUPCOL: 223 if (pip.hasProtocol(ProtocolIdentification.Protocol.ConfigurationDescription)) { 224 var snip = nodememo.getSimpleNodeIdent(); 225 String name = (snip != null) ? snip.getUserName() : "<unknown>"; 226 var backuper = new jmri.jmrix.openlcb.swing.lccpro.NodeBackupAction(); 227 backuper.doBackup(nodememo, memo, name); 228 // We want the table to retain focus while the CDI loads 229 // This also removes the selection from this cell, so that cmd-` 230 // no longer repeats the action of pressing the button 231 forceFocus(); 232 } 233 break; 234 default: 235 break; 236 } 237 } 238 239 // to be overridden at construction time with e.g. 240 // frame.toFront(); 241 // frame.requestFocus(); 242 // 243 public void forceFocus() { 244 } 245 246 public int getPreferredWidth(int column) { 247 int retval = 20; // always take some width 248 retval = Math.max(retval, new JLabel(getColumnName(column)) 249 .getPreferredSize().width + 15); // leave room for sorter arrow 250 for (int row = 0; row < getRowCount(); row++) { 251 if (getColumnClass(column).equals(String.class)) { 252 retval = Math.max(retval, new JLabel(getValueAt(row, column).toString()).getPreferredSize().width); 253 } else if (getColumnClass(column).equals(Integer.class)) { 254 retval = Math.max(retval, new JLabel(getValueAt(row, column).toString()).getPreferredSize().width); 255 } else if (getColumnClass(column).equals(ImageIcon.class)) { 256 retval = Math.max(retval, new JLabel((Icon) getValueAt(row, column)).getPreferredSize().width); 257 } 258 } 259 return retval + 5; 260 } 261 262 // drop listeners 263 public void dispose() { 264 nodestore.removePropertyChangeListener(this); 265 } 266 267 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LccProTableModel.class); 268}