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