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}