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