001package jmri.jmrit.operations.trains.gui;
002
003import java.awt.*;
004import java.beans.PropertyChangeEvent;
005import java.beans.PropertyChangeListener;
006import java.util.Hashtable;
007import java.util.List;
008
009import javax.swing.*;
010import javax.swing.table.DefaultTableCellRenderer;
011import javax.swing.table.TableCellEditor;
012
013import jmri.InstanceManager;
014import jmri.jmrit.beantable.EnablingCheckboxRenderer;
015import jmri.jmrit.operations.routes.Route;
016import jmri.jmrit.operations.setup.Control;
017import jmri.jmrit.operations.setup.Setup;
018import jmri.jmrit.operations.trains.Train;
019import jmri.jmrit.operations.trains.TrainManager;
020import jmri.util.swing.JmriJOptionPane;
021import jmri.util.swing.XTableColumnModel;
022import jmri.util.table.ButtonEditor;
023import jmri.util.table.ButtonRenderer;
024
025/**
026 * Table Model for edit of trains used by operations
027 *
028 * @author Daniel Boudreau Copyright (C) 2008, 2012
029 */
030public class TrainsTableModel extends javax.swing.table.AbstractTableModel implements PropertyChangeListener {
031
032    TrainManager trainManager = InstanceManager.getDefault(TrainManager.class); // There is only one manager
033    volatile List<Train> sysList = trainManager.getTrainsByTimeList();
034    JTable _table = null;
035    TrainsTableFrame _frame = null;
036
037    // Defines the columns
038    private static final int ID_COLUMN = 0;
039    private static final int TIME_COLUMN = ID_COLUMN + 1;
040    private static final int BUILDBOX_COLUMN = TIME_COLUMN + 1;
041    private static final int BUILD_COLUMN = BUILDBOX_COLUMN + 1;
042    private static final int NAME_COLUMN = BUILD_COLUMN + 1;
043    private static final int DESCRIPTION_COLUMN = NAME_COLUMN + 1;
044    private static final int BUILT_COLUMN = DESCRIPTION_COLUMN + 1;
045    private static final int CAR_ROAD_COLUMN = BUILT_COLUMN + 1;
046    private static final int CABOOSE_ROAD_COLUMN = CAR_ROAD_COLUMN + 1;
047    private static final int LOCO_ROAD_COLUMN = CABOOSE_ROAD_COLUMN + 1;
048    private static final int LOAD_COLUMN = LOCO_ROAD_COLUMN + 1;
049    private static final int OWNER_COLUMN = LOAD_COLUMN + 1;
050    private static final int ROUTE_COLUMN = OWNER_COLUMN + 1;
051    private static final int DEPARTS_COLUMN = ROUTE_COLUMN + 1;
052    private static final int TERMINATES_COLUMN = DEPARTS_COLUMN + 1;
053    private static final int CURRENT_COLUMN = TERMINATES_COLUMN + 1;
054    private static final int CARS_COLUMN = CURRENT_COLUMN + 1;
055    private static final int STATUS_COLUMN = CARS_COLUMN + 1;
056    private static final int ACTION_COLUMN = STATUS_COLUMN + 1;
057    private static final int EDIT_COLUMN = ACTION_COLUMN + 1;
058
059    private static final int HIGHESTCOLUMN = EDIT_COLUMN + 1;
060
061    public TrainsTableModel() {
062        super();
063        trainManager.addPropertyChangeListener(this);
064        Setup.getDefault().addPropertyChangeListener(this);
065        updateList();
066    }
067
068    public final int SORTBYTIME = 2;
069    public final int SORTBYID = 7;
070
071    private int _sort = SORTBYTIME;
072
073    public void setSort(int sort) {
074        _sort = sort;
075        updateList();
076        updateColumnVisible();
077    }
078
079    private boolean _showAll = true;
080
081    public void setShowAll(boolean showAll) {
082        _showAll = showAll;
083        updateList();
084        fireTableDataChanged();
085    }
086
087    public boolean isShowAll() {
088        return _showAll;
089    }
090
091    private void updateList() {
092        // first, remove listeners from the individual objects
093        removePropertyChangeTrains();
094
095        List<Train> tempList;
096        if (_sort == SORTBYID) {
097            tempList = trainManager.getTrainsByIdList();
098        } else {
099            tempList = trainManager.getTrainsByTimeList();
100        }
101
102        if (!isShowAll()) {
103            // filter out trains not checked
104            for (int i = tempList.size() - 1; i >= 0; i--) {
105                if (!tempList.get(i).isBuildEnabled()) {
106                    tempList.remove(i);
107                }
108            }
109        }
110        sysList = tempList;
111
112        // and add listeners back in
113        addPropertyChangeTrains();
114    }
115
116    private Train getTrainByRow(int row) {
117        return sysList.get(row);
118    }
119
120    void initTable(JTable table, TrainsTableFrame frame) {
121        _table = table;
122        _frame = frame;
123        // allow row color to be controlled
124        table.setDefaultRenderer(Object.class, new MyTableCellRenderer());
125        table.setDefaultRenderer(Integer.class, new MyTableCellRenderer());
126        initTable();
127    }
128
129    // Train frame table column widths, starts with id column and ends with edit
130    private final int[] _tableColumnWidths =
131            {50, 50, 50, 72, 100, 140, 50, 50, 50, 50, 50, 50, 120, 120, 120, 120, 50, 120, 90,
132                    70};
133
134    void initTable() {
135        // Use XTableColumnModel so we can control which columns are visible
136        XTableColumnModel tcm = new XTableColumnModel();
137        _table.setColumnModel(tcm);
138        _table.createDefaultColumnsFromModel();
139
140        // Install the button handlers
141        ButtonRenderer buttonRenderer = new ButtonRenderer();
142        ButtonRenderer buttonRenderer2 = new ButtonRenderer(); // for tool tips
143        TableCellEditor buttonEditor = new ButtonEditor(new javax.swing.JButton());
144        tcm.getColumn(EDIT_COLUMN).setCellRenderer(buttonRenderer);
145        tcm.getColumn(EDIT_COLUMN).setCellEditor(buttonEditor);
146        tcm.getColumn(ACTION_COLUMN).setCellRenderer(buttonRenderer);
147        tcm.getColumn(ACTION_COLUMN).setCellEditor(buttonEditor);
148        tcm.getColumn(BUILD_COLUMN).setCellRenderer(buttonRenderer2);
149        tcm.getColumn(BUILD_COLUMN).setCellEditor(buttonEditor);
150        _table.setDefaultRenderer(Boolean.class, new EnablingCheckboxRenderer());
151
152        // set column preferred widths
153        for (int i = 0; i < tcm.getColumnCount(); i++) {
154            tcm.getColumn(i).setPreferredWidth(_tableColumnWidths[i]);
155        }
156        _frame.loadTableDetails(_table);
157
158        // turn off column
159        updateColumnVisible();
160    }
161
162    private void updateColumnVisible() {
163        XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
164        tcm.setColumnVisible(tcm.getColumnByModelIndex(ID_COLUMN), _sort == SORTBYID);
165        tcm.setColumnVisible(tcm.getColumnByModelIndex(TIME_COLUMN), _sort == SORTBYTIME);
166        tcm.setColumnVisible(tcm.getColumnByModelIndex(BUILT_COLUMN), trainManager.isBuiltRestricted());
167        tcm.setColumnVisible(tcm.getColumnByModelIndex(CAR_ROAD_COLUMN), trainManager.isCarRoadRestricted());
168        tcm.setColumnVisible(tcm.getColumnByModelIndex(CABOOSE_ROAD_COLUMN), trainManager.isCabooseRoadRestricted());
169        tcm.setColumnVisible(tcm.getColumnByModelIndex(LOCO_ROAD_COLUMN), trainManager.isLocoRoadRestricted());
170        tcm.setColumnVisible(tcm.getColumnByModelIndex(LOAD_COLUMN), trainManager.isLoadRestricted());
171        tcm.setColumnVisible(tcm.getColumnByModelIndex(OWNER_COLUMN), trainManager.isOwnerRestricted());
172    }
173
174    @Override
175    public int getRowCount() {
176        return sysList.size();
177    }
178
179    @Override
180    public int getColumnCount() {
181        return HIGHESTCOLUMN;
182    }
183
184    public static final String IDCOLUMNNAME = Bundle.getMessage("Id");
185    public static final String TIMECOLUMNNAME = Bundle.getMessage("Time");
186    public static final String BUILDBOXCOLUMNNAME = Bundle.getMessage("Build");
187    public static final String BUILDCOLUMNNAME = Bundle.getMessage("Function");
188    public static final String NAMECOLUMNNAME = Bundle.getMessage("Name");
189    public static final String DESCRIPTIONCOLUMNNAME = Bundle.getMessage("Description");
190    public static final String ROUTECOLUMNNAME = Bundle.getMessage("Route");
191    public static final String DEPARTSCOLUMNNAME = Bundle.getMessage("Departs");
192    public static final String CURRENTCOLUMNNAME = Bundle.getMessage("Current");
193    public static final String TERMINATESCOLUMNNAME = Bundle.getMessage("Terminates");
194    public static final String STATUSCOLUMNNAME = Bundle.getMessage("Status");
195    public static final String ACTIONCOLUMNNAME = Bundle.getMessage("Action");
196    public static final String EDITCOLUMNNAME = Bundle.getMessage("ButtonEdit");
197
198    @Override
199    public String getColumnName(int col) {
200        switch (col) {
201            case ID_COLUMN:
202                return IDCOLUMNNAME;
203            case TIME_COLUMN:
204                return TIMECOLUMNNAME;
205            case BUILDBOX_COLUMN:
206                return BUILDBOXCOLUMNNAME;
207            case BUILD_COLUMN:
208                return BUILDCOLUMNNAME;
209            case NAME_COLUMN:
210                return NAMECOLUMNNAME;
211            case DESCRIPTION_COLUMN:
212                return DESCRIPTIONCOLUMNNAME;
213            case BUILT_COLUMN:
214                return Bundle.getMessage("Built");
215            case CAR_ROAD_COLUMN:
216                return Bundle.getMessage("RoadsCar");
217            case CABOOSE_ROAD_COLUMN:
218                return Bundle.getMessage("RoadsCaboose");
219            case LOCO_ROAD_COLUMN:
220                return Bundle.getMessage("RoadsLoco");
221            case LOAD_COLUMN:
222                return Bundle.getMessage("Load");
223            case OWNER_COLUMN:
224                return Bundle.getMessage("Owner");
225            case ROUTE_COLUMN:
226                return ROUTECOLUMNNAME;
227            case DEPARTS_COLUMN:
228                return DEPARTSCOLUMNNAME;
229            case CURRENT_COLUMN:
230                return CURRENTCOLUMNNAME;
231            case TERMINATES_COLUMN:
232                return TERMINATESCOLUMNNAME;
233            case CARS_COLUMN:
234                return Bundle.getMessage("Cars");
235            case STATUS_COLUMN:
236                return STATUSCOLUMNNAME;
237            case ACTION_COLUMN:
238                return ACTIONCOLUMNNAME;
239            case EDIT_COLUMN:
240                return EDITCOLUMNNAME;
241            default:
242                return "unknown"; // NOI18N
243        }
244    }
245
246    @Override
247    public Class<?> getColumnClass(int col) {
248        switch (col) {
249            case BUILDBOX_COLUMN:
250                return Boolean.class;
251            case ID_COLUMN:
252            case CARS_COLUMN:
253                return Integer.class;
254            case TIME_COLUMN:
255            case NAME_COLUMN:
256            case DESCRIPTION_COLUMN:
257            case BUILT_COLUMN:
258            case CAR_ROAD_COLUMN:
259            case CABOOSE_ROAD_COLUMN:
260            case LOCO_ROAD_COLUMN:
261            case LOAD_COLUMN:
262            case OWNER_COLUMN:
263            case ROUTE_COLUMN:
264            case DEPARTS_COLUMN:
265            case CURRENT_COLUMN:
266            case TERMINATES_COLUMN:
267            case STATUS_COLUMN:
268                return String.class;
269            case BUILD_COLUMN:
270            case ACTION_COLUMN:
271            case EDIT_COLUMN:
272                return JButton.class;
273            default:
274                return null;
275        }
276    }
277
278    @Override
279    public boolean isCellEditable(int row, int col) {
280        switch (col) {
281            case BUILD_COLUMN:
282            case BUILDBOX_COLUMN:
283            case ACTION_COLUMN:
284            case EDIT_COLUMN:
285                return true;
286            default:
287                return false;
288        }
289    }
290
291    @Override
292    public Object getValueAt(int row, int col) {
293        if (row >= getRowCount()) {
294            return "ERROR row " + row; // NOI18N
295        }
296        Train train = getTrainByRow(row);
297        if (train == null) {
298            return "ERROR train unknown " + row; // NOI18N
299        }
300        switch (col) {
301            case ID_COLUMN:
302                return Integer.parseInt(train.getId());
303            case TIME_COLUMN:
304                return train.getDepartureTime();
305            case NAME_COLUMN:
306                return train.getIconName();
307            case DESCRIPTION_COLUMN:
308                return train.getDescription();
309            case BUILDBOX_COLUMN:
310                return Boolean.valueOf(train.isBuildEnabled());
311            case BUILT_COLUMN:
312                return getBuiltString(train);
313            case CAR_ROAD_COLUMN:
314                return getModifiedString(train.getCarRoadNames().length,
315                        train.getCarRoadOption().equals(Train.ALL_ROADS),
316                        train.getCarRoadOption().equals(Train.INCLUDE_ROADS));
317            case CABOOSE_ROAD_COLUMN:
318                return getModifiedString(train.getCabooseRoadNames().length,
319                        train.getCabooseRoadOption().equals(Train.ALL_ROADS),
320                        train.getCabooseRoadOption().equals(Train.INCLUDE_ROADS));
321            case LOCO_ROAD_COLUMN:
322                return getModifiedString(train.getLocoRoadNames().length,
323                        train.getLocoRoadOption().equals(Train.ALL_ROADS),
324                        train.getLocoRoadOption().equals(Train.INCLUDE_ROADS));
325            case LOAD_COLUMN:
326                return getModifiedString(train.getLoadNames().length, train.getLoadOption().equals(Train.ALL_LOADS),
327                        train.getLoadOption().equals(Train.INCLUDE_LOADS));
328            case OWNER_COLUMN:
329                return getModifiedString(train.getOwnerNames().length, train.getOwnerOption().equals(Train.ALL_OWNERS),
330                        train.getOwnerOption().equals(Train.INCLUDE_OWNERS));
331            case ROUTE_COLUMN:
332                return train.getTrainRouteName();
333            case DEPARTS_COLUMN: {
334                if (train.getDepartureTrack() == null) {
335                    return train.getTrainDepartsName();
336                } else {
337                    return train.getTrainDepartsName() + " (" + train.getDepartureTrack().getName() + ")";
338                }
339            }
340            case CURRENT_COLUMN:
341                return train.getCurrentLocationName();
342            case TERMINATES_COLUMN: {
343                if (train.getTerminationTrack() == null) {
344                    return train.getTrainTerminatesName();
345                } else {
346                    return train.getTrainTerminatesName() + " (" + train.getTerminationTrack().getName() + ")";
347                }
348            }
349            case CARS_COLUMN:
350                return train.getNumberCarsInTrain();
351            case STATUS_COLUMN:
352                return train.getStatus();
353            case BUILD_COLUMN: {
354                if (train.isBuilt()) {
355                    if (Setup.isGenerateCsvManifestEnabled() && trainManager.isOpenFileEnabled()) {
356                        setToolTip(Bundle.getMessage("OpenTrainTip", train.getName()), col);
357                        return Bundle.getMessage("OpenFile");
358                    }
359                    if (Setup.isGenerateCsvManifestEnabled() && trainManager.isRunFileEnabled()) {
360                        setToolTip(Bundle.getMessage("RunTrainTip", train.getName()), col);
361                        return Bundle.getMessage("RunFile");
362                    }
363                    setToolTip(Bundle.getMessage("PrintTrainTip"), col);
364                    if (trainManager.isPrintPreviewEnabled()) {
365                        return Bundle.getMessage("Preview");
366                    } else if (train.isPrinted()) {
367                        return Bundle.getMessage("Printed");
368                    } else {
369                        return Bundle.getMessage("Print");
370                    }
371                }
372                setToolTip(Bundle.getMessage("BuildTrainTip", train.getName()), col);
373                return Bundle.getMessage("Build");
374            }
375            case ACTION_COLUMN: {
376                if (train.isBuildFailed()) {
377                    return Bundle.getMessage("Report");
378                }
379                if (train.getCurrentRouteLocation() == train.getTrainTerminatesRouteLocation() &&
380                        trainManager.getTrainsFrameTrainAction().equals(TrainsTableFrame.MOVE)) {
381                    return Bundle.getMessage("Terminate");
382                }
383                return trainManager.getTrainsFrameTrainAction();
384            }
385            case EDIT_COLUMN:
386                return Bundle.getMessage("ButtonEdit");
387            default:
388                return "unknown " + col; // NOI18N
389        }
390    }
391
392    private void setToolTip(String text, int col) {
393        XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
394        ButtonRenderer buttonRenderer = (ButtonRenderer) tcm.getColumnByModelIndex(col).getCellRenderer();
395        if (buttonRenderer != null) {
396            buttonRenderer.setToolTipText(text);
397        }
398    }
399
400    private String getBuiltString(Train train) {
401        if (!train.getBuiltStartYear().equals(Train.NONE) && train.getBuiltEndYear().equals(Train.NONE)) {
402            return "A " + train.getBuiltStartYear();
403        }
404        if (train.getBuiltStartYear().equals(Train.NONE) && !train.getBuiltEndYear().equals(Train.NONE)) {
405            return "B " + train.getBuiltEndYear();
406        }
407        if (!train.getBuiltStartYear().equals(Train.NONE) && !train.getBuiltEndYear().equals(Train.NONE)) {
408            return "R " + train.getBuiltStartYear() + ":" + train.getBuiltEndYear();
409        }
410        return "";
411    }
412
413    private String getModifiedString(int number, boolean all, boolean accept) {
414        if (all) {
415            return "";
416        }
417        if (accept) {
418            return "A " + Integer.toString(number); // NOI18N
419        }
420        return "E " + Integer.toString(number); // NOI18N
421    }
422
423    @Override
424    public void setValueAt(Object value, int row, int col) {
425        switch (col) {
426            case EDIT_COLUMN:
427                editTrain(row);
428                break;
429            case BUILD_COLUMN:
430                buildTrain(row);
431                break;
432            case ACTION_COLUMN:
433                actionTrain(row);
434                break;
435            case BUILDBOX_COLUMN: {
436                Train train = getTrainByRow(row);
437                train.setBuildEnabled(((Boolean) value).booleanValue());
438                break;
439            }
440            default:
441                break;
442        }
443    }
444
445    public Color getRowColor(int row) {
446        Train train = getTrainByRow(row);
447        return train.getTableRowColor();
448    }
449
450    TrainEditFrame tef = null;
451
452    private void editTrain(int row) {
453        if (tef != null) {
454            tef.dispose();
455        }
456        // use invokeLater so new window appears on top
457        SwingUtilities.invokeLater(() -> {
458            Train train = getTrainByRow(row);
459            log.debug("Edit train ({})", train.getName());
460            tef = new TrainEditFrame(train);
461        });
462    }
463
464    Thread build;
465
466    private void buildTrain(int row) {
467        final Train train = getTrainByRow(row);
468        if (!train.isBuilt()) {
469            // only one train build at a time
470            if (build != null && build.isAlive()) {
471                return;
472            }
473            // use a thread to allow table updates during build
474            build = jmri.util.ThreadingUtil.newThread(new Runnable() {
475                @Override
476                public void run() {
477                    train.build();
478                }
479            });
480            build.setName("Build Train (" + train.getName() + ")"); // NOI18N
481            build.start();
482            // print build report, print manifest, run or open file
483        } else {
484            if (trainManager.isBuildReportEnabled()) {
485                train.printBuildReport();
486            }
487            if (Setup.isGenerateCsvManifestEnabled() && trainManager.isOpenFileEnabled()) {
488                train.openFile();
489            } else if (Setup.isGenerateCsvManifestEnabled() && trainManager.isRunFileEnabled()) {
490                train.runFile();
491            } else {
492                if (!train.printManifestIfBuilt()) {
493                    log.debug("Manifest file for train ({}) not found", train.getName());
494                    int result = JmriJOptionPane.showConfirmDialog(null,
495                            Bundle.getMessage("TrainManifestFileMissing",
496                                    train.getName()),
497                            Bundle.getMessage("TrainManifestFileError"), JmriJOptionPane.YES_NO_OPTION);
498                    if (result == JmriJOptionPane.YES_OPTION) {
499                        train.setModified(true);
500                        if (!train.printManifestIfBuilt()) {
501                            log.error("Unable to create manifest for train ({})", train.getName());
502                        }
503                    }
504                }
505            }
506        }
507    }
508
509    // one of five buttons: Report, Move, Reset, Conductor or Terminate
510    private void actionTrain(int row) {
511        // no actions while a train is being built
512        if (build != null && build.isAlive()) {
513            return;
514        }
515        Train train = getTrainByRow(row);
516        // move button becomes report if failure
517        if (train.isBuildFailed()) {
518            train.printBuildReport();
519        } else if (trainManager.getTrainsFrameTrainAction().equals(TrainsTableFrame.RESET)) {
520            log.debug("Reset train ({})", train.getName());
521            // check to see if departure track was reused
522            if (train.checkDepartureTrack()) {
523                log.debug("Train is departing staging that already has inbound cars");
524                JmriJOptionPane.showMessageDialog(null,
525                        Bundle.getMessage("StagingTrackUsed",
526                                train.getDepartureTrack().getName()),
527                        Bundle.getMessage("CanNotResetTrain"), JmriJOptionPane.INFORMATION_MESSAGE);
528            } else if (!train.reset()) {
529                JmriJOptionPane.showMessageDialog(null,
530                        Bundle.getMessage("TrainIsInRoute",
531                                train.getTrainTerminatesName()),
532                        Bundle.getMessage("CanNotResetTrain"), JmriJOptionPane.ERROR_MESSAGE);
533            }
534        } else if (!train.isBuilt()) {
535            int reply = JmriJOptionPane.showOptionDialog(null,
536                    Bundle.getMessage("TrainNeedsBuild", train.getName()),
537                    Bundle.getMessage("CanNotPerformAction"), JmriJOptionPane.NO_OPTION,
538                    JmriJOptionPane.INFORMATION_MESSAGE, null,
539                    new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("Build")}, null);
540            if (reply == 1) {
541                train.build();
542            }
543        } else if (train.isBuilt() && trainManager.getTrainsFrameTrainAction().equals(TrainsTableFrame.MOVE)) {
544            log.debug("Move train ({})", train.getName());
545            train.move();
546        } else if (train.isBuilt() && trainManager.getTrainsFrameTrainAction().equals(TrainsTableFrame.TERMINATE)) {
547            log.debug("Terminate train ({})", train.getName());
548            int status = JmriJOptionPane.showConfirmDialog(null,
549                    Bundle.getMessage("TerminateTrain",
550                            train.getName(), train.getDescription()),
551                    Bundle.getMessage("DoYouWantToTermiate", train.getName()),
552                    JmriJOptionPane.YES_NO_OPTION);
553            if (status == JmriJOptionPane.YES_OPTION) {
554                train.terminate();
555            }
556        } else if (train.isBuilt() && trainManager.getTrainsFrameTrainAction().equals(TrainsTableFrame.CONDUCTOR)) {
557            log.debug("Enable conductor for train ({})", train.getName());
558            launchConductor(train);
559        }
560    }
561
562    private static Hashtable<String, TrainConductorFrame> _trainConductorHashTable = new Hashtable<>();
563
564    private void launchConductor(Train train) {
565        // use invokeLater so new window appears on top
566        SwingUtilities.invokeLater(() -> {
567            TrainConductorFrame f = _trainConductorHashTable.get(train.getId());
568            // create a copy train frame
569            if (f == null || !f.isVisible()) {
570                f = new TrainConductorFrame(train);
571                _trainConductorHashTable.put(train.getId(), f);
572            } else {
573                f.setExtendedState(Frame.NORMAL);
574            }
575            f.setVisible(true); // this also brings the frame into focus
576        });
577    }
578
579    @Override
580    public void propertyChange(PropertyChangeEvent e) {
581        if (Control.SHOW_PROPERTY) {
582            log.debug("Property change {} old: {} new: {}", e.getPropertyName(), e.getOldValue(), e.getNewValue()); // NOI18N
583        }
584        if (e.getPropertyName().equals(Train.BUILT_YEAR_CHANGED_PROPERTY) ||
585                e.getPropertyName().equals(Train.ROADS_CHANGED_PROPERTY) ||
586                e.getPropertyName().equals(Train.LOADS_CHANGED_PROPERTY) ||
587                e.getPropertyName().equals(Train.OWNERS_CHANGED_PROPERTY)) {
588            updateColumnVisible();
589        }
590        if (e.getPropertyName().equals(TrainManager.LISTLENGTH_CHANGED_PROPERTY) ||
591                e.getPropertyName().equals(TrainManager.PRINTPREVIEW_CHANGED_PROPERTY) ||
592                e.getPropertyName().equals(TrainManager.OPEN_FILE_CHANGED_PROPERTY) ||
593                e.getPropertyName().equals(TrainManager.RUN_FILE_CHANGED_PROPERTY) ||
594                e.getPropertyName().equals(Setup.MANIFEST_CSV_PROPERTY_CHANGE) ||
595                e.getPropertyName().equals(TrainManager.TRAIN_ACTION_CHANGED_PROPERTY) ||
596                e.getPropertyName().equals(Train.DEPARTURETIME_CHANGED_PROPERTY) ||
597                (e.getPropertyName().equals(Train.BUILD_CHANGED_PROPERTY) && !isShowAll())) {
598            SwingUtilities.invokeLater(() -> {
599                updateList();
600                fireTableDataChanged();
601            });
602        } else if (e.getSource().getClass().equals(Train.class) &&
603                !e.getPropertyName().equals(Route.ROUTE_STATUS_CHANGED_PROPERTY)) {
604            Train train = ((Train) e.getSource());
605            SwingUtilities.invokeLater(() -> {
606                int row = sysList.indexOf(train);
607                if (row >= 0 && _table != null) {
608                    fireTableRowsUpdated(row, row);
609                    int viewRow = _table.convertRowIndexToView(row);
610                    log.debug("Scroll table to row: {}, train: {}, property: {}", viewRow, train.getName(),
611                            e.getPropertyName());
612                    _table.scrollRectToVisible(_table.getCellRect(viewRow, 0, true));
613                }
614            });
615        }
616    }
617
618    private void removePropertyChangeTrains() {
619        for (Train train : trainManager.getList()) {
620            train.removePropertyChangeListener(this);
621        }
622    }
623
624    private void addPropertyChangeTrains() {
625        for (Train train : trainManager.getList()) {
626            train.addPropertyChangeListener(this);
627        }
628    }
629
630    public void dispose() {
631        if (tef != null) {
632            tef.dispose();
633        }
634        trainManager.removePropertyChangeListener(this);
635        Setup.getDefault().removePropertyChangeListener(this);
636        removePropertyChangeTrains();
637    }
638
639    class MyTableCellRenderer extends DefaultTableCellRenderer {
640
641        @Override
642        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
643                int row, int column) {
644            Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
645            if (!isSelected) {
646                int modelRow = table.convertRowIndexToModel(row);
647                // log.debug("View row: {} Column: {} Model row: {}", row, column, modelRow);
648                Color background = getRowColor(modelRow);
649                component.setBackground(background);
650                component.setForeground(getForegroundColor(background));
651            }
652            return component;
653        }
654
655        Color[] darkColors = {Color.BLACK, Color.BLUE, Color.GRAY, Color.RED, Color.MAGENTA};
656
657        /**
658         * Dark colors need white lettering
659         */
660        private Color getForegroundColor(Color background) {
661            if (background == null) {
662                return null;
663            }
664            for (Color color : darkColors) {
665                if (background == color) {
666                    return Color.WHITE;
667                }
668            }
669            return Color.BLACK; // all others get black lettering
670        }
671    }
672
673    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainsTableModel.class);
674}