001package jmri.jmrix.sprog.sprogslotmon;
002
003import javax.swing.JLabel;
004import javax.swing.JTable;
005import javax.swing.JTextField;
006import jmri.jmrix.sprog.SprogConstants;
007import jmri.jmrix.sprog.SprogSlot;
008import jmri.jmrix.sprog.SprogSlotListener;
009import jmri.jmrix.sprog.SprogSystemConnectionMemo;
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013/**
014 * Table data model for display of slot manager contents.
015 *
016 * @author Bob Jacobsen Copyright (C) 2001 
017 * @author  Andrew Crosland (C) 2006 ported to SPROG
018 */
019public class SprogSlotMonDataModel extends javax.swing.table.AbstractTableModel implements SprogSlotListener {
020
021    static public final int SLOTCOLUMN = 0;
022    static public final int ADDRCOLUMN = 1;
023    static public final int SPDCOLUMN = 2;
024    static public final int STATCOLUMN = 3;  // status: free, common, etc
025    static public final int DIRCOLUMN = 4;
026
027    static public final int NUMCOLUMN = 5;
028
029    private SprogSystemConnectionMemo _memo = null;
030
031    SprogSlotMonDataModel(int row, int column,SprogSystemConnectionMemo memo) {
032        _memo = memo;
033        // connect to SprogSlotManager for updates
034        _memo.getCommandStation().addSlotListener(this);
035    }
036
037    /**
038     * Return the number of rows to be displayed. This can vary depending on
039     * whether only active rows are displayed.
040     * <p>
041     * This should probably use a local cache instead of counting/searching each
042     * time.
043     */
044    @Override
045    public int getRowCount() {
046        int nMax = _memo.getNumSlots();
047        if (_allSlots) {
048            // will show the entire set, so don't bother counting
049            return nMax;
050        }
051        int n = 0;
052        int nMin = 0;
053        for (int i = nMin; i < nMax; i++) {
054            SprogSlot s = _memo.getCommandStation().slot(i);
055            if (s.isFree() != true) {
056                n++;
057            }
058        }
059        return n;
060    }
061
062    @Override
063    public int getColumnCount() {
064        return NUMCOLUMN;
065    }
066
067    @Override
068    public String getColumnName(int col) {
069        switch (col) {
070            case SLOTCOLUMN:
071                return Bundle.getMessage("SlotCol");
072//        case ESTOPCOLUMN: return "";     // no heading, as button is clear
073            case ADDRCOLUMN:
074                return Bundle.getMessage("AddressCol");
075            case SPDCOLUMN:
076                return Bundle.getMessage("SpeedCol");
077            case STATCOLUMN:
078                return Bundle.getMessage("StatusCol");
079//        case CONSCOLUMN: return "Consisted";
080            case DIRCOLUMN:
081                return Bundle.getMessage("DirectionCol");
082//        case DISPCOLUMN: return "";     // no heading, as button is clear
083            default:
084                return "unknown"; // NOI18N
085        }
086    }
087
088    @Override
089    public Class<?> getColumnClass(int col) {
090        switch (col) {
091            case SLOTCOLUMN:
092                return Integer.class;
093            case ADDRCOLUMN:
094            case SPDCOLUMN:
095            case STATCOLUMN:
096//        case CONSCOLUMN:
097            case DIRCOLUMN:
098                return String.class;
099//        case ESTOPCOLUMN:
100//        case DISPCOLUMN:
101//            return JButton.class;
102            default:
103                return null;
104        }
105    }
106
107    @Override
108    public boolean isCellEditable(int row, int col) {
109        switch (col) {
110//        case ESTOPCOLUMN:
111//        case DISPCOLUMN:
112//            return true;
113            default:
114                return false;
115        }
116    }
117
118    static final Boolean True = Boolean.valueOf("True");
119    static final Boolean False = Boolean.valueOf("False");
120
121    @SuppressWarnings("null")
122    @Override
123    public Object getValueAt(int row, int col) {
124        SprogSlot s = _memo.getCommandStation().slot(slotNum(row));
125        if (s == null) {
126            log.error("slot pointer was null for slot row: {} col: {}", row, col);
127        }
128
129        switch (col) {
130            case SLOTCOLUMN:  // slot number
131                return Integer.valueOf(slotNum(row));
132//        case ESTOPCOLUMN:  //
133//            return "E Stop";          // will be name of button in default GUI
134            case ADDRCOLUMN:  //
135                    switch (s.slotStatus()) {
136                        case SprogConstants.SLOT_IN_USE:
137                            return Integer.toString(s.getAddr()) + "("+ (s.getIsLong() ? Bundle.getMessage("LongAddressChar") : Bundle.getMessage("ShortAddressChar")) + ")";
138                        case SprogConstants.SLOT_FREE:
139                            return "-";
140                        default:
141                            return Bundle.getMessage("StateError");
142                    }
143            case SPDCOLUMN:  //
144                switch (s.slotStatus()) {
145                    case SprogConstants.SLOT_IN_USE:
146                        if (s.isF0to4Packet()) {
147                            return "F0to4Pkt";
148                        } else if (s.isF5to8Packet()) {
149                            return "F5to8Pkt";
150                        } else if (s.isF9to12Packet()) {
151                            return "F9to12Pkt";
152                        } else if (s.isF13to20Packet()) {
153                            return "F13to20Pkt";
154                        } else if (s.isF21to28Packet()) {
155                            return "F21to28Pkt";
156                        } else if (s.isOpsPkt()) {
157                            return "OpsPkt";
158                        } else if (s.isSpeedPacket()) {
159                            String t;
160                            if (s.speed() == 1) {
161                                t = "(estop) 1";
162                            } else {
163                                t = "          " + s.speed();
164                            }
165                            return t.substring(t.length() - 9, t.length()); // 9 comes from (estop)
166                        } else {
167                          return Bundle.getMessage("StateError");
168                        }
169                    case SprogConstants.SLOT_FREE:
170                        return "-";
171                    default:
172                        return Bundle.getMessage("StateError");
173                }
174            case STATCOLUMN:  //
175                switch (s.slotStatus()) {
176                    case SprogConstants.SLOT_IN_USE:
177                        return Bundle.getMessage("StateInUse");
178                    case SprogConstants.SLOT_FREE:
179                        return Bundle.getMessage("StateFree");
180                    default:
181                        return Bundle.getMessage("StateError");
182                }
183//        case CONSCOLUMN:  //
184//            return "<n/a>";
185//        case DISPCOLUMN:  //
186//            return Bundle.getMessage("ButtonRelease"); // will be name of button in default GUI
187            case DIRCOLUMN:  //
188                    switch (s.slotStatus()) {
189                        case SprogConstants.SLOT_IN_USE:
190                            if (s.isSpeedPacket()) {
191                                return (s.isForward() ? Bundle.getMessage("DirColForward") : Bundle.getMessage("DirColReverse"));
192                            } else {
193                                return "-";                               
194                            }
195                        case SprogConstants.SLOT_FREE:
196                            return "-";
197                        default:
198                            return Bundle.getMessage("StateError");
199                    }
200
201            default:
202                log.error("internal state inconsistent with table request for row {}, col {}", row, col);
203                return null;
204        }
205    }
206
207    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES", 
208                        justification="better to keep cases in column order rather than to combine")
209    public int getPreferredWidth(int col) {
210        switch (col) {
211            case SLOTCOLUMN:
212                return new JTextField(3).getPreferredSize().width;
213//        case ESTOPCOLUMN:
214//            return new JButton("E Stop").getPreferredSize().width;
215            case ADDRCOLUMN:
216                return new JTextField(5).getPreferredSize().width;
217            case SPDCOLUMN:
218                return new JTextField(6).getPreferredSize().width;
219            case STATCOLUMN:
220                return new JTextField(6).getPreferredSize().width;
221//        case CONSCOLUMN:
222//            return new JTextField(4).getPreferredSize().width;
223            case DIRCOLUMN:
224                return new JTextField(3).getPreferredSize().width;
225//        case DISPCOLUMN:
226//            return new JButton(Bundle.getMessage("ButtonRelease")).getPreferredSize().width;
227            default:
228                return new JLabel(" <unknown> ").getPreferredSize().width; // NOI18N
229        }
230    }
231
232    @Override
233    public void setValueAt(Object value, int row, int col) {
234        // check for in use
235        SprogSlot s = _memo.getCommandStation().slot(slotNum(row));
236        if (s == null) {
237            log.error("slot pointer was null for slot row: {} col: {}", row, col);
238            return;
239        }
240//        if (col == ESTOPCOLUMN) {
241//            log.debug("Start eStop in slot "+row);
242//            _memo.getSlotManager().estopSlot(row);
243//        }
244//        else if (col == DISPCOLUMN) {
245//            log.debug("Start freeing slot {}", row);
246//            fireTableRowsUpdated(row,row);
247//        }
248    }
249
250    /**
251     * Configure a table to have our standard rows and columns.
252     * This is optional, in that other table formats can use this table model. 
253     * But we put it here to help keep it consistent.
254     *
255     * @param slotTable the slot table to configure.
256     */
257    public void configureTable(JTable slotTable) {
258        // allow reordering of the columns
259        slotTable.getTableHeader().setReorderingAllowed(true);
260
261        // shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
262        slotTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
263
264        // resize columns as requested
265        for (int i = 0; i < slotTable.getColumnCount(); i++) {
266            int width = getPreferredWidth(i);
267            slotTable.getColumnModel().getColumn(i).setPreferredWidth(width);
268        }
269        slotTable.sizeColumnsToFit(-1);
270
271//        // install a button renderer & editor in the "DISP" column for freeing a slot
272//        setColumnToHoldButton(slotTable, SprogSlotMonDataModel.DISPCOLUMN);
273//
274//        // install a button renderer & editor in the "ESTOP" column for stopping a loco
275//        setColumnToHoldEStopButton(slotTable, SprogSlotMonDataModel.ESTOPCOLUMN);
276    }
277
278//    void setColumnToHoldButton(JTable slotTable, int column) {
279//        TableColumnModel tcm = slotTable.getColumnModel();
280//        // install the button renderers & editors in this column
281//        ButtonRenderer buttonRenderer = new ButtonRenderer();
282//        tcm.getColumn(column).setCellRenderer(buttonRenderer);
283//        TableCellEditor buttonEditor = new ButtonEditor(new JButton());
284//        tcm.getColumn(column).setCellEditor(buttonEditor);
285//        // ensure the table rows, columns have enough room for buttons
286//        slotTable.setRowHeight(new JButton("  "+getValueAt(1, column)).getPreferredSize().height);
287//        slotTable.getColumnModel().getColumn(column)
288//                .setPreferredWidth(new JButton("  "+getValueAt(1, column)).getPreferredSize().width);
289//    }
290//
291//    void setColumnToHoldEStopButton(JTable slotTable, int column) {
292//        TableColumnModel tcm = slotTable.getColumnModel();
293//        // install the button renderers & editors in this column
294//        ButtonRenderer buttonRenderer = new ButtonRenderer();
295//        tcm.getColumn(column).setCellRenderer(buttonRenderer);
296//        TableCellEditor buttonEditor = new ButtonEditor(new JButton()){
297//            public void mousePressed(MouseEvent e) {
298//                stopCellEditing();
299//            }
300//        };
301//        tcm.getColumn(column).setCellEditor(buttonEditor);
302//        // ensure the table rows, columns have enough room for buttons
303//        slotTable.setRowHeight(new JButton("  "+getValueAt(1, column)).getPreferredSize().height);
304//        slotTable.getColumnModel().getColumn(column)
305//                .setPreferredWidth(new JButton("  "+getValueAt(1, column)).getPreferredSize().width);
306//    }
307    // methods to communicate with SprogSlotManager
308    @Override
309    public synchronized void notifyChangedSlot(SprogSlot s) {
310        // update model from this slot
311
312        int slotNum = -1;
313        if (_allSlots) {          // this will be row until we show only active slots
314            slotNum = s.getSlotNumber();  // and we are displaying the System slots
315        }
316        log.debug("Received notification of changed slot: {}", slotNum);
317        // notify the JTable object that a row has changed; do that in the Swing thread!
318        Runnable r = new Notify(slotNum, this);   // -1 in first arg means all
319        javax.swing.SwingUtilities.invokeLater(r);
320    }
321
322    static class Notify implements Runnable {
323
324        private int _row;
325        javax.swing.table.AbstractTableModel _model;
326
327        public Notify(int row, javax.swing.table.AbstractTableModel model) {
328            _row = row;
329            _model = model;
330        }
331
332        @Override
333        public void run() {
334            if (-1 == _row) {  // notify about entire table
335                _model.fireTableDataChanged();  // just that row
336            } else {
337                // notify that _row has changed
338                _model.fireTableRowsUpdated(_row, _row);  // just that row
339            }
340        }
341    }
342
343    // methods for control of "all slots" vs "only active slots"
344    private boolean _allSlots = true;
345
346    public void showAllSlots(boolean val) {
347        _allSlots = val;
348    }
349
350    /**
351     * Return slot number for a specific row.
352     * <p>
353     * This should probably use a local cache instead of counting/searching each
354     * time.
355     *
356     * @param row Row number in the displayed table
357     * @return Matching slot number
358     */
359    protected int slotNum(int row) {
360        // ??? Can't this just return row ???
361        int slotNum;
362        int n = -1;   // need to find a used slot to have the 0th one!
363        int nMin = 0;
364        int nMax = _memo.getNumSlots();
365        for (slotNum = nMin; slotNum < nMax; slotNum++) {
366            SprogSlot s = _memo.getCommandStation().slot(slotNum);
367            if (_allSlots || s.slotStatus() != SprogConstants.SLOT_FREE) {
368                n++;
369            }
370            if (n == row) {
371                break;
372            }
373        }
374        return slotNum;
375    }
376
377    public void dispose() {
378        _memo.getCommandStation().removeSlotListener(this);
379        // table.removeAllElements();
380        // table = null;
381    }
382
383    private final static Logger log = LoggerFactory.getLogger(SprogSlotMonDataModel.class);
384
385}