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