001package jmri.jmrit.automat.monitor;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.ArrayList;
006
007import javax.swing.JButton;
008import javax.swing.JTable;
009import javax.swing.JTextField;
010import javax.swing.SwingUtilities;
011import javax.swing.table.AbstractTableModel;
012import javax.swing.table.TableCellEditor;
013import javax.swing.table.TableColumnModel;
014
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017
018import jmri.jmrit.automat.AbstractAutomaton;
019import jmri.jmrit.automat.AutomatSummary;
020import jmri.util.table.ButtonEditor;
021import jmri.util.table.ButtonRenderer;
022
023/**
024 * Table data model for display of Automat instances.
025 *
026 *
027 * @author Bob Jacobsen Copyright (C) 2004
028 */
029public class AutomatTableDataModel extends AbstractTableModel {
030
031    static final int NAMECOL = 0; // display name
032    static final int TURNSCOL = 1; // number of times through the loop
033    static final int KILLCOL = 2; //
034
035    static final int NUMCOLUMN = 3;
036
037    AutomatSummary summary = AutomatSummary.instance();
038    private ArrayList<AbstractAutomaton> automats = summary.getAutomats();
039
040    private final PropertyChangeListener listener = (PropertyChangeEvent evt) -> {
041        SwingUtilities.invokeLater(() -> {
042            // the number of automations can't not change during table update
043            automats = summary.getAutomats();
044            switch (evt.getPropertyName()) {
045                case "Insert":
046                    // fireTableRowsInserted(((Integer)e.getNewValue()).intValue(),
047                    // ((Integer)e.getNewValue()).intValue());
048                    fireTableDataChanged();
049                    break;
050                case "Remove":
051                    // fireTableRowsDeleted(((Integer)e.getNewValue()).intValue(),
052                    // ((Integer)e.getNewValue()).intValue());
053                    fireTableDataChanged();
054                    break;
055                case "Count":
056                    // it's a count indication, so update TURNS
057                    int row = ((Integer) evt.getNewValue());
058                    // length might have changed...
059                    if (row < getRowCount()) {
060                        fireTableCellUpdated(row, TURNSCOL);
061                    }
062                    break;
063                default:
064                    log.debug("Ignoring unexpected property {}", evt.getPropertyName());
065                    break;
066            }
067        });
068    };
069
070    public AutomatTableDataModel() {
071        super();
072        // listen for new/gone/changed Automat instances
073        summary.addPropertyChangeListener(this.listener);
074    }
075
076    @Override
077    public int getColumnCount() {
078        return NUMCOLUMN;
079    }
080
081    @Override
082    public int getRowCount() {
083        return automats.size();
084    }
085
086    @Override
087    public String getColumnName(int col) {
088        switch (col) {
089            case NAMECOL:
090                return Bundle.getMessage("ColName");
091            case TURNSCOL:
092                return Bundle.getMessage("ColCycles");
093            case KILLCOL:
094                return Bundle.getMessage("ColKill"); // problem if this is blank?
095
096            default:
097                return Bundle.getMessage("ColUnknown");
098        }
099    }
100
101    /**
102     * Note that this returns String even for columns that contain buttons.
103     * {@inheritDoc}
104     */
105    @Override
106    public Class<?> getColumnClass(int col) {
107        switch (col) {
108            case NAMECOL:
109            case KILLCOL:
110                return String.class;
111            case TURNSCOL:
112                return Integer.class;
113            default:
114                return null;
115        }
116    }
117
118    @Override
119    public boolean isCellEditable(int row, int col) {
120        switch (col) {
121            case KILLCOL:
122                return true;
123            default:
124                return false;
125        }
126    }
127
128    @Override
129    public Object getValueAt(int row, int col) {
130        AbstractAutomaton automat = automats.get(row);
131        if (automat != null) {
132            switch (col) {
133                case NAMECOL:
134                    return automat.getName();
135                case TURNSCOL:
136                    return automat.getCount();
137                case KILLCOL: // return button text here
138                    return Bundle.getMessage("ButtonKill");
139                default:
140                    log.error("internal state inconsistent with table requst for {} {}", row, col);
141                    return null;
142            }
143        }
144        return null;
145    }
146
147    public int getPreferredWidth(int col) {
148        switch (col) {
149            case NAMECOL:
150                return new JTextField(20).getPreferredSize().width;
151            case TURNSCOL:
152                return new JTextField(5).getPreferredSize().width;
153            case KILLCOL:
154                return new JButton(Bundle.getMessage("ButtonKill")).getPreferredSize().width;
155            default:
156                log.warn("Unexpected column in getPreferredWidth: {}", col);
157                return new JTextField(5).getPreferredSize().width;
158        }
159    }
160
161    @Override
162    public void setValueAt(Object value, int row, int col) {
163        if (col == KILLCOL) {
164            // button fired, handle
165            summary.get(row).stop();
166        }
167    }
168
169    /**
170     * Configure a table to have our standard rows and columns. This is optional, in
171     * that other table formats can use this table model. But we put it here to help
172     * keep it consistent.
173     *
174     * @param table the table to configure
175     */
176    public void configureTable(JTable table) {
177        // allow reordering of the columns
178        table.getTableHeader().setReorderingAllowed(true);
179
180        // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p
181        // 541)
182        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
183
184        // resize columns as requested
185        for (int i = 0; i < table.getColumnCount(); i++) {
186            int width = getPreferredWidth(i);
187            table.getColumnModel().getColumn(i).setPreferredWidth(width);
188        }
189        table.sizeColumnsToFit(-1);
190
191        // have the value column hold a button
192        setColumnToHoldButton(table, KILLCOL, new JButton(Bundle.getMessage("ButtonKill")));
193    }
194
195    /**
196     * Service method to setup a column so that it will hold a button for its values
197     *
198     * @param table  the table in which to configure the column
199     * @param column the position of the configured column
200     * @param sample typical button, used for size
201     */
202    void setColumnToHoldButton(JTable table, int column, JButton sample) {
203        TableColumnModel tcm = table.getColumnModel();
204        // install a button renderer & editor
205        ButtonRenderer buttonRenderer = new ButtonRenderer();
206        tcm.getColumn(column).setCellRenderer(buttonRenderer);
207        TableCellEditor buttonEditor = new ButtonEditor(new JButton());
208        tcm.getColumn(column).setCellEditor(buttonEditor);
209        // ensure the table rows, columns have enough room for buttons
210        table.setRowHeight(sample.getPreferredSize().height);
211        table.getColumnModel().getColumn(column).setPreferredWidth(sample.getPreferredSize().width);
212    }
213
214    synchronized public void dispose() {
215        AutomatSummary.instance().removePropertyChangeListener(this.listener);
216    }
217
218    private final static Logger log = LoggerFactory.getLogger(AutomatTableDataModel.class);
219
220}