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