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 * <p> 021 * 022 * @author Bob Jacobsen Copyright (C) 2009, 2010, 2024 023 * @since 5.11.1 024 */ 025public class LccProTableModel extends DefaultTableModel implements PropertyChangeListener { 026 027 static final int NAMECOL = 0; 028 public static final int IDCOL = 1; 029 static final int MFGCOL = 2; 030 static final int MODELCOL = 3; 031 static final int SVERSIONCOL = 4; 032 public static final int CONFIGURECOL = 5; 033 public static final int UPGRADECOL = 6; 034 public static final int NUMCOL = UPGRADECOL + 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 default: 097 return "<unexpected column number>"; 098 } 099 } 100 101 @Override 102 public Class<?> getColumnClass(int col) { 103 switch (col) { 104 case CONFIGURECOL: 105 case UPGRADECOL: 106 return JButton.class; 107 default: 108 return String.class; 109 } 110 } 111 112 /** 113 * {@inheritDoc} 114 * <p> 115 * Note that the table can be set to be non-editable when constructed, in 116 * which case this always returns false. 117 * 118 * @return true if cell is editable 119 */ 120 @Override 121 public boolean isCellEditable(int row, int col) { 122 switch (col) { 123 case CONFIGURECOL: 124 case UPGRADECOL: 125 return true; 126 default: 127 return false; 128 } 129 } 130 131 /** 132 * {@inheritDoc} 133 * 134 * Provides an empty string for a column if the model returns null for that 135 * value. 136 */ 137 @Override 138 public Object getValueAt(int row, int col) { 139 log.trace("getValue({}, {})", row, col); 140 141 var memoArray = nodestore.getNodeMemos().toArray(new MimicNodeStore.NodeMemo[0]); 142 if (row >= memoArray.length) return ""; // sometimes happens at startup 143 var nodememo = memoArray[row]; 144 if (nodememo == null) return "<invalid node memo>"; 145 var snip = nodememo.getSimpleNodeIdent(); 146 if (snip == null) return "<snip info not yet availble>"; 147 var pip = nodememo.getProtocolIdentification(); 148 149 switch (col) { 150 case NAMECOL: 151 return snip.getUserName(); 152 case IDCOL: 153 return nodememo.getNodeID().toString(); 154 case MFGCOL: 155 return snip.getMfgName(); 156 case MODELCOL: 157 return snip.getModelName(); 158 case SVERSIONCOL: 159 return snip.getSoftwareVersion(); 160 case CONFIGURECOL: 161 if (pip.hasProtocol(ProtocolIdentification.Protocol.ConfigurationDescription)) { 162 return Bundle.getMessage("FieldConfig"); 163 } else { 164 return null; 165 } 166 case UPGRADECOL: 167 if (pip.hasProtocol(ProtocolIdentification.Protocol.FirmwareUpgrade)) { 168 return Bundle.getMessage("FieldUpgrade"); 169 } else { 170 return null; 171 } 172 default: 173 return "<unexpected column number>"; 174 } 175 } 176 177 @Override 178 public void setValueAt(Object value, int row, int col) { 179 log.trace("getValue({}, {})", row, col); 180 MimicNodeStore.NodeMemo nodememo = nodestore.getNodeMemos().toArray(new MimicNodeStore.NodeMemo[0])[row]; 181 if (nodememo == null) { 182 log.error("Button pushed but no corresponding node for row {}", row); 183 return; 184 } 185 var pip = nodememo.getProtocolIdentification(); 186 187 switch (col) { 188 case CONFIGURECOL: 189 if (pip.hasProtocol(ProtocolIdentification.Protocol.ConfigurationDescription)) { 190 var actions = new jmri.jmrix.openlcb.swing.ClientActions(memo.get(org.openlcb.OlcbInterface.class), memo); 191 var node = nodememo.getNodeID(); 192 var description = jmri.jmrix.openlcb.swing.networktree.NetworkTreePane.augmentedNodeName(nodememo); 193 actions.openCdiWindow(node, description); 194 // We want the table to retain focus while the CDI loads 195 // This also removes the selection from this cell, so that cmd-` 196 // no longer repeats the action of pressing the button 197 forceFocus(); 198 } 199 break; 200 case UPGRADECOL: 201 if (pip.hasProtocol(ProtocolIdentification.Protocol.FirmwareUpgrade)) { 202 var node = nodememo.getNodeID(); 203 var action = new jmri.jmrix.openlcb.swing.downloader.LoaderPane.Default(node); 204 action.actionPerformed(null); 205 } 206 break; 207 default: 208 // TODO - fire the buttons 209 break; 210 } 211 } 212 213 // to be overridden at construction time with e.g. 214 // frame.toFront(); 215 // frame.requestFocus(); 216 // 217 public void forceFocus() { 218 } 219 220 public int getPreferredWidth(int column) { 221 int retval = 20; // always take some width 222 retval = Math.max(retval, new JLabel(getColumnName(column)) 223 .getPreferredSize().width + 15); // leave room for sorter arrow 224 for (int row = 0; row < getRowCount(); row++) { 225 if (getColumnClass(column).equals(String.class)) { 226 retval = Math.max(retval, new JLabel(getValueAt(row, column).toString()).getPreferredSize().width); 227 } else if (getColumnClass(column).equals(Integer.class)) { 228 retval = Math.max(retval, new JLabel(getValueAt(row, column).toString()).getPreferredSize().width); 229 } else if (getColumnClass(column).equals(ImageIcon.class)) { 230 retval = Math.max(retval, new JLabel((Icon) getValueAt(row, column)).getPreferredSize().width); 231 } 232 } 233 return retval + 5; 234 } 235 236 // drop listeners 237 public void dispose() { 238 nodestore.removePropertyChangeListener(this); 239 } 240 241 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LccProTableModel.class); 242}