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}