001package jmri.jmrit.operations.rollingstock.engines.gui;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.List;
006
007import javax.swing.*;
008import javax.swing.table.TableCellEditor;
009
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013import jmri.InstanceManager;
014import jmri.jmrit.operations.OperationsTableModel;
015import jmri.jmrit.operations.rollingstock.RollingStock;
016import jmri.jmrit.operations.rollingstock.engines.*;
017import jmri.jmrit.operations.setup.Control;
018import jmri.jmrit.operations.setup.Setup;
019import jmri.util.swing.XTableColumnModel;
020import jmri.util.table.ButtonEditor;
021import jmri.util.table.ButtonRenderer;
022
023/**
024 * Table Model for edit of engines used by operations
025 *
026 * @author Daniel Boudreau Copyright (C) 2008, 2012, 2025
027 */
028public class EnginesTableModel extends OperationsTableModel implements PropertyChangeListener {
029
030    EngineManager engineManager = InstanceManager.getDefault(EngineManager.class); // There is only one manager
031
032    // Defines the columns
033    private static final int SELECT_COLUMN = 0;
034    private static final int NUM_COLUMN = 1;
035    private static final int ROAD_COLUMN = 2;
036    private static final int MODEL_COLUMN = 3;
037    private static final int HP_COLUMN = 4;
038    private static final int WEIGHT_COLUMN = 5;
039    private static final int TYPE_COLUMN = 6;
040    private static final int LENGTH_COLUMN = 7;
041    private static final int CONSIST_COLUMN = 8;
042    private static final int LOCATION_COLUMN = 9;
043    private static final int RFID_WHERE_LAST_SEEN_COLUMN = 10;
044    private static final int RFID_WHEN_LAST_SEEN_COLUMN = 11;
045    private static final int DESTINATION_COLUMN = 12;
046    private static final int PREVIOUS_LOCATION_COLUMN = 13;
047    private static final int TRAIN_COLUMN = 14;
048    private static final int LAST_TRAIN_COLUMN = 15;
049    private static final int MOVES_COLUMN = 16;
050    private static final int BUILT_COLUMN = 17;
051    private static final int OWNER_COLUMN = 18;
052    private static final int VALUE_COLUMN = 19;
053    private static final int RFID_COLUMN = 20;
054    private static final int LAST_COLUMN = 21;
055    private static final int DCC_ADDRESS_COLUMN = 22;
056    private static final int COMMENT_COLUMN = 23;
057    private static final int SET_COLUMN = 24;
058    private static final int EDIT_COLUMN = 25;
059
060    private static final int HIGHEST_COLUMN = EDIT_COLUMN + 1;
061
062    public EnginesTableModel(boolean showAllLocos, String locationName, String trackName) {
063        super();
064        showAll = showAllLocos;
065        this.locationName = locationName;
066        this.trackName = trackName;
067        engineManager.addPropertyChangeListener(this);
068        updateList();
069    }
070
071    public final int SORTBY_NUMBER = 0;
072    public final int SORTBY_ROAD = 1;
073    public final int SORTBY_MODEL = 2;
074    public final int SORTBY_LOCATION = 3;
075    public final int SORTBY_DESTINATION = 4;
076    public final int SORTBY_TRAIN = 5;
077    public final int SORTBY_MOVES = 6;
078    public final int SORTBY_CONSIST = 7;
079    public final int SORTBY_BUILT = 8;
080    public final int SORTBY_OWNER = 9;
081    public final int SORTBY_VALUE = 10;
082    public final int SORTBY_RFID = 11;
083    public final int SORTBY_LAST = 12;
084    public final int SORTBY_HP = 13;
085    public final int SORTBY_DCC_ADDRESS = 14;
086    public final int SORTBY_COMMENT = 15;
087
088    private int _sort = SORTBY_NUMBER;
089
090    /**
091     * Not all columns are visible at the same time.
092     *
093     * @param sort which sort is active
094     */
095    public void setSort(int sort) {
096        _sort = sort;
097        updateList();
098        if (sort == SORTBY_MOVES ||
099                sort == SORTBY_BUILT ||
100                sort == SORTBY_OWNER ||
101                sort == SORTBY_VALUE ||
102                sort == SORTBY_RFID ||
103                sort == SORTBY_LAST ||
104                sort == SORTBY_DCC_ADDRESS ||
105                sort == SORTBY_COMMENT) {
106            XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
107            tcm.setColumnVisible(tcm.getColumnByModelIndex(MOVES_COLUMN), sort == SORTBY_MOVES);
108            tcm.setColumnVisible(tcm.getColumnByModelIndex(BUILT_COLUMN), sort == SORTBY_BUILT);
109            tcm.setColumnVisible(tcm.getColumnByModelIndex(OWNER_COLUMN), sort == SORTBY_OWNER);
110            tcm.setColumnVisible(tcm.getColumnByModelIndex(VALUE_COLUMN), sort == SORTBY_VALUE);
111            tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_COLUMN), sort == SORTBY_RFID);
112            tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHEN_LAST_SEEN_COLUMN), sort == SORTBY_RFID);
113            tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHERE_LAST_SEEN_COLUMN), sort == SORTBY_RFID);
114            tcm.setColumnVisible(tcm.getColumnByModelIndex(PREVIOUS_LOCATION_COLUMN), sort == SORTBY_LAST);
115            tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_COLUMN), sort == SORTBY_LAST);
116            tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_TRAIN_COLUMN), sort == SORTBY_LAST);
117            tcm.setColumnVisible(tcm.getColumnByModelIndex(TRAIN_COLUMN), sort != SORTBY_LAST);
118            tcm.setColumnVisible(tcm.getColumnByModelIndex(DCC_ADDRESS_COLUMN), sort == SORTBY_DCC_ADDRESS);
119            tcm.setColumnVisible(tcm.getColumnByModelIndex(COMMENT_COLUMN), sort == SORTBY_COMMENT);
120        }
121        fireTableDataChanged();
122    }
123
124    public void toggleSelectVisible() {
125        XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
126        tcm.setColumnVisible(tcm.getColumnByModelIndex(SELECT_COLUMN),
127                !tcm.isColumnVisible(tcm.getColumnByModelIndex(SELECT_COLUMN)));
128    }
129
130    public void resetCheckboxes() {
131        for (Engine engine : engineList) {
132            engine.setSelected(false);
133        }
134    }
135
136    public String getSortByName() {
137        return getSortByName(_sort);
138    }
139
140    public String getSortByName(int sort) {
141        switch (sort) {
142            case SORTBY_NUMBER:
143                return Bundle.getMessage("Number");
144            case SORTBY_ROAD:
145                return Bundle.getMessage("Road");
146            case SORTBY_MODEL:
147                return Bundle.getMessage("Model");
148            case SORTBY_LOCATION:
149                return Bundle.getMessage("Location");
150            case SORTBY_DESTINATION:
151                return Bundle.getMessage("Destination");
152            case SORTBY_TRAIN:
153                return Bundle.getMessage("Train");
154            case SORTBY_MOVES:
155                return Bundle.getMessage("Moves");
156            case SORTBY_CONSIST:
157                return Bundle.getMessage("Consist");
158            case SORTBY_BUILT:
159                return Bundle.getMessage("Built");
160            case SORTBY_OWNER:
161                return Bundle.getMessage("Owner");
162            case SORTBY_DCC_ADDRESS:
163                return Bundle.getMessage("DccAddress");
164            case SORTBY_HP:
165                return Bundle.getMessage("HP");
166            case SORTBY_VALUE:
167                return Setup.getValueLabel();
168            case SORTBY_RFID:
169                return Setup.getRfidLabel();
170            case SORTBY_LAST:
171                return Bundle.getMessage("Last");
172            case SORTBY_COMMENT:
173                return Bundle.getMessage("Comment");
174            default:
175                return "Error"; // NOI18N
176        }
177    }
178
179    /**
180     * Search for engine by road number
181     * 
182     * @param roadNumber The string road number to search for.
183     *
184     * @return -1 if not found, table row number if found
185     */
186    public int findEngineByRoadNumber(String roadNumber) {
187        return findRollingStockByRoadNumber(roadNumber, engineList);
188    }
189
190    public Engine getEngineAtIndex(int index) {
191        return engineList.get(index);
192    }
193
194    private void updateList() {
195        // first, remove listeners from the individual objects
196        removePropertyChangeEngines();
197        engineList = getSelectedEngineList();
198        // and add listeners back in
199        addPropertyChangeEngines();
200    }
201
202    public List<Engine> getSelectedEngineList() {
203        return getEngineList(_sort);
204    }
205
206    public List<Engine> getEngineList(int sort) {
207        List<Engine> list;
208        switch (sort) {
209            case SORTBY_ROAD:
210                list = engineManager.getByRoadNameList();
211                break;
212            case SORTBY_MODEL:
213                list = engineManager.getByModelList();
214                break;
215            case SORTBY_LOCATION:
216                list = engineManager.getByLocationList();
217                break;
218            case SORTBY_DESTINATION:
219                list = engineManager.getByDestinationList();
220                break;
221            case SORTBY_TRAIN:
222                list = engineManager.getByTrainList();
223                break;
224            case SORTBY_MOVES:
225                list = engineManager.getByMovesList();
226                break;
227            case SORTBY_CONSIST:
228                list = engineManager.getByConsistList();
229                break;
230            case SORTBY_OWNER:
231                list = engineManager.getByOwnerList();
232                break;
233            case SORTBY_BUILT:
234                list = engineManager.getByBuiltList();
235                break;
236            case SORTBY_VALUE:
237                list = engineManager.getByValueList();
238                break;
239            case SORTBY_RFID:
240                list = engineManager.getByRfidList();
241                break;
242            case SORTBY_LAST:
243                list = engineManager.getByLastDateList();
244                break;
245            case SORTBY_COMMENT:
246                list = engineManager.getByCommentList();
247                break;
248            case SORTBY_NUMBER:
249            default:
250                list = engineManager.getByNumberList();
251        }
252        filterList(list);
253        return list;
254    }
255
256    List<Engine> engineList = null;
257
258    EnginesTableFrame _frame;
259
260    void initTable(JTable table, EnginesTableFrame frame) {
261        _table = table;
262        _frame = frame;
263        initTable();
264    }
265
266    // Default engines frame table column widths, starts with Number column and ends with Edit
267    private final int[] _enginesTableColumnWidths =
268            {60, 60, 60, 65, 50, 65, 65, 35, 75, 190, 190, 190, 140, 190, 65, 90, 50, 50, 50, 50, 100, 130, 50, 100, 65,
269                    70};
270
271    void initTable() {
272        // Use XTableColumnModel so we can control which columns are visible
273        XTableColumnModel tcm = new XTableColumnModel();
274        _table.setColumnModel(tcm);
275        _table.createDefaultColumnsFromModel();
276
277        // Install the button handlers
278        ButtonRenderer buttonRenderer = new ButtonRenderer();
279        tcm.getColumn(SET_COLUMN).setCellRenderer(buttonRenderer);
280        TableCellEditor buttonEditor = new ButtonEditor(new javax.swing.JButton());
281        tcm.getColumn(SET_COLUMN).setCellEditor(buttonEditor);
282        tcm.getColumn(EDIT_COLUMN).setCellRenderer(buttonRenderer);
283        tcm.getColumn(EDIT_COLUMN).setCellEditor(buttonEditor);
284
285        // set column preferred widths
286        // load defaults, xml file data not found
287        for (int i = 0; i < tcm.getColumnCount(); i++) {
288            tcm.getColumn(i).setPreferredWidth(_enginesTableColumnWidths[i]);
289        }
290        _frame.loadTableDetails(_table);
291
292        // turn off columns
293        tcm.setColumnVisible(tcm.getColumnByModelIndex(BUILT_COLUMN), false);
294        tcm.setColumnVisible(tcm.getColumnByModelIndex(OWNER_COLUMN), false);
295        tcm.setColumnVisible(tcm.getColumnByModelIndex(VALUE_COLUMN), false);
296        tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_COLUMN), false);
297        tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHEN_LAST_SEEN_COLUMN), false);
298        tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHERE_LAST_SEEN_COLUMN), false);
299        tcm.setColumnVisible(tcm.getColumnByModelIndex(PREVIOUS_LOCATION_COLUMN), false);
300        tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_COLUMN), false);
301        tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_TRAIN_COLUMN), false);
302        tcm.setColumnVisible(tcm.getColumnByModelIndex(DCC_ADDRESS_COLUMN), false);
303        tcm.setColumnVisible(tcm.getColumnByModelIndex(COMMENT_COLUMN), false);
304
305        // turn on default
306        tcm.setColumnVisible(tcm.getColumnByModelIndex(MOVES_COLUMN), true);
307    }
308
309    @Override
310    public int getRowCount() {
311        return engineList.size();
312    }
313
314    @Override
315    public int getColumnCount() {
316        return HIGHEST_COLUMN;
317    }
318
319    @Override
320    public String getColumnName(int col) {
321        switch (col) {
322            case SELECT_COLUMN:
323                return Bundle.getMessage("ButtonSelect");
324            case NUM_COLUMN:
325                return Bundle.getMessage("Number");
326            case ROAD_COLUMN:
327                return Bundle.getMessage("Road");
328            case MODEL_COLUMN:
329                return Bundle.getMessage("Model");
330            case HP_COLUMN:
331                return Bundle.getMessage("HP");
332            case TYPE_COLUMN:
333                return Bundle.getMessage("Type");
334            case LENGTH_COLUMN:
335                return Bundle.getMessage("Len");
336            case WEIGHT_COLUMN:
337                return Bundle.getMessage("Weight");
338            case CONSIST_COLUMN:
339                return Bundle.getMessage("Consist");
340            case LOCATION_COLUMN:
341                return Bundle.getMessage("Location");
342            case RFID_WHERE_LAST_SEEN_COLUMN:
343                return Bundle.getMessage("WhereLastSeen");
344            case RFID_WHEN_LAST_SEEN_COLUMN:
345                return Bundle.getMessage("WhenLastSeen");
346            case DESTINATION_COLUMN:
347                return Bundle.getMessage("Destination");
348            case PREVIOUS_LOCATION_COLUMN:
349                return Bundle.getMessage("LastLocation");
350            case TRAIN_COLUMN:
351                return Bundle.getMessage("Train");
352            case LAST_TRAIN_COLUMN:
353                return Bundle.getMessage("LastTrain");
354            case MOVES_COLUMN:
355                return Bundle.getMessage("Moves");
356            case BUILT_COLUMN:
357                return Bundle.getMessage("Built");
358            case OWNER_COLUMN:
359                return Bundle.getMessage("Owner");
360            case VALUE_COLUMN:
361                return Setup.getValueLabel();
362            case RFID_COLUMN:
363                return Setup.getRfidLabel();
364            case LAST_COLUMN:
365                return Bundle.getMessage("LastMoved");
366            case DCC_ADDRESS_COLUMN:
367                return Bundle.getMessage("DccAddress");
368            case COMMENT_COLUMN:
369                return Bundle.getMessage("Comment");
370            case SET_COLUMN:
371                return Bundle.getMessage("Set");
372            case EDIT_COLUMN:
373                return Bundle.getMessage("ButtonEdit"); // titles above all columns
374            default:
375                return "unknown"; // NOI18N
376        }
377    }
378
379    @Override
380    public Class<?> getColumnClass(int col) {
381        switch (col) {
382            case SELECT_COLUMN:
383                return Boolean.class;
384            case SET_COLUMN:
385            case EDIT_COLUMN:
386                return JButton.class;
387            case LENGTH_COLUMN:
388            case MOVES_COLUMN:
389                return Integer.class;
390            default:
391                return String.class;
392        }
393    }
394
395    @Override
396    public boolean isCellEditable(int row, int col) {
397        switch (col) {
398            case SELECT_COLUMN:
399            case SET_COLUMN:
400            case EDIT_COLUMN:
401            case MOVES_COLUMN:
402            case VALUE_COLUMN:
403            case RFID_COLUMN:
404                return true;
405            default:
406                return false;
407        }
408    }
409
410    @Override
411    public Object getValueAt(int row, int col) {
412        if (row >= getRowCount()) {
413            return "ERROR row " + row; // NOI18N
414        }
415        Engine engine = engineList.get(row);
416        if (engine == null) {
417            return "ERROR engine unknown " + row; // NOI18N
418        }
419        switch (col) {
420            case SELECT_COLUMN:
421                return engine.isSelected();
422            case NUM_COLUMN:
423                return engine.getNumber();
424            case ROAD_COLUMN:
425                return engine.getRoadName();
426            case LENGTH_COLUMN:
427                return engine.getLengthInteger();
428            case MODEL_COLUMN:
429                return engine.getModel();
430            case HP_COLUMN:
431                return engine.getHp();
432            case TYPE_COLUMN: {
433                if (engine.isBunit()) {
434                    return engine.getTypeName() + " " + Bundle.getMessage("(B)");
435                }
436                return engine.getTypeName();
437            }
438            case WEIGHT_COLUMN:
439                return engine.getWeightTons();
440            case CONSIST_COLUMN: {
441                if (engine.isLead()) {
442                    return engine.getConsistName() + "*";
443                }
444                return engine.getConsistName();
445            }
446            case LOCATION_COLUMN: {
447                String s = engine.getStatus();
448                if (!engine.getLocationName().equals(Engine.NONE)) {
449                    s = engine.getStatus() + engine.getLocationName() + " (" + engine.getTrackName() + ")";
450                }
451                return s;
452            }
453            case RFID_WHERE_LAST_SEEN_COLUMN: {
454                return engine.getWhereLastSeenName() +
455                        (engine.getTrackLastSeenName().equals(Engine.NONE) ? "" : " (" + engine.getTrackLastSeenName() + ")");
456            }
457            case RFID_WHEN_LAST_SEEN_COLUMN: {
458                return engine.getWhenLastSeenDate();
459            }
460            case DESTINATION_COLUMN: {
461                String s = "";
462                if (!engine.getDestinationName().equals(Engine.NONE)) {
463                    s = engine.getDestinationName() + " (" + engine.getDestinationTrackName() + ")";
464                }
465                return s;
466            }
467            case PREVIOUS_LOCATION_COLUMN: {
468                String s = "";
469                if (!engine.getLastLocationName().equals(Engine.NONE)) {
470                    s = engine.getLastLocationName() + " (" + engine.getLastTrackName() + ")";
471                }
472                return s;
473            }
474            case TRAIN_COLUMN: {
475                // if train was manually set by user add an asterisk
476                if (engine.getTrain() != null && engine.getRouteLocation() == null) {
477                    return engine.getTrainName() + "*";
478                }
479                return engine.getTrainName();
480            }
481            case LAST_TRAIN_COLUMN:
482                return engine.getLastTrainName();
483            case MOVES_COLUMN:
484                return engine.getMoves();
485            case BUILT_COLUMN:
486                return engine.getBuilt();
487            case OWNER_COLUMN:
488                return engine.getOwnerName();
489            case VALUE_COLUMN:
490                return engine.getValue();
491            case RFID_COLUMN:
492                return engine.getRfid();
493            case LAST_COLUMN:
494                return engine.getSortDate();
495            case DCC_ADDRESS_COLUMN:
496                return engine.getDccAddress();
497            case COMMENT_COLUMN:
498                return engine.getComment();
499            case SET_COLUMN:
500                return Bundle.getMessage("Set");
501            case EDIT_COLUMN:
502                return Bundle.getMessage("ButtonEdit");
503            default:
504                return "unknown " + col; // NOI18N
505        }
506    }
507
508    EngineEditFrame engineEditFrame = null;
509    EngineSetFrame engineSetFrame = null;
510
511    @Override
512    public void setValueAt(Object value, int row, int col) {
513        Engine engine = engineList.get(row);
514        switch (col) {
515            case SELECT_COLUMN:
516                engine.setSelected(((Boolean) value).booleanValue());
517                break;
518            case MOVES_COLUMN:
519                try {
520                    engine.setMoves(Integer.parseInt(value.toString()));
521                } catch (NumberFormatException e) {
522                    log.error("move count must be a number");
523                }
524                break;
525            case BUILT_COLUMN:
526                engine.setBuilt(value.toString());
527                break;
528            case OWNER_COLUMN:
529                engine.setOwnerName(value.toString());
530                break;
531            case VALUE_COLUMN:
532                engine.setValue(value.toString());
533                break;
534            case RFID_COLUMN:
535                engine.setRfid(value.toString());
536                break;
537            case SET_COLUMN:
538                log.debug("Set engine location");
539                if (engineSetFrame != null) {
540                    engineSetFrame.dispose();
541                }
542                // use invokeLater so new window appears on top
543                SwingUtilities.invokeLater(() -> {
544                    engineSetFrame = new EngineSetFrame();
545                    engineSetFrame.initComponents();
546                    engineSetFrame.load(engine);
547                });
548                break;
549            case EDIT_COLUMN:
550                log.debug("Edit engine");
551                if (engineEditFrame != null) {
552                    engineEditFrame.dispose();
553                }
554                // use invokeLater so new window appears on top
555                SwingUtilities.invokeLater(() -> {
556                    engineEditFrame = new EngineEditFrame();
557                    engineEditFrame.initComponents();
558                    engineEditFrame.load(engine);
559                });
560                break;
561            default:
562                break;
563        }
564    }
565
566    public void dispose() {
567        log.debug("dispose EngineTableModel");
568        engineManager.removePropertyChangeListener(this);
569        removePropertyChangeEngines();
570        if (engineSetFrame != null) {
571            engineSetFrame.dispose();
572        }
573        if (engineEditFrame != null) {
574            engineEditFrame.dispose();
575        }
576    }
577
578    private void addPropertyChangeEngines() {
579        for (RollingStock rs : engineManager.getList()) {
580            rs.addPropertyChangeListener(this);
581        }
582    }
583
584    private void removePropertyChangeEngines() {
585        for (RollingStock rs : engineManager.getList()) {
586            rs.removePropertyChangeListener(this);
587        }
588    }
589
590    @Override
591    public void propertyChange(PropertyChangeEvent e) {
592        if (Control.SHOW_PROPERTY) {
593            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
594                    .getNewValue());
595        }
596        if (e.getPropertyName().equals(EngineManager.LISTLENGTH_CHANGED_PROPERTY) ||
597                e.getPropertyName().equals(ConsistManager.LISTLENGTH_CHANGED_PROPERTY)) {
598            updateList();
599            fireTableDataChanged();
600        }
601        // Engine length, type, and HP are based on model, so multiple changes
602        else if (e.getPropertyName().equals(Engine.LENGTH_CHANGED_PROPERTY) ||
603                e.getPropertyName().equals(Engine.TYPE_CHANGED_PROPERTY) ||
604                e.getPropertyName().equals(Engine.HP_CHANGED_PROPERTY)) {
605            fireTableDataChanged();
606        }
607        // must be a engine change
608        else if (e.getSource().getClass().equals(Engine.class)) {
609            Engine engine = (Engine) e.getSource();
610            int row = engineList.indexOf(engine);
611            if (Control.SHOW_PROPERTY) {
612                log.debug("Update engine table row: {}", row);
613            }
614            if (row >= 0) {
615                fireTableRowsUpdated(row, row);
616                // next is needed when only showing engines at a location or track
617            } else if (e.getPropertyName().equals(Engine.TRACK_CHANGED_PROPERTY)) {
618                updateList();
619                fireTableDataChanged();
620            }
621        }
622    }
623
624    private final static Logger log = LoggerFactory.getLogger(EnginesTableModel.class);
625}