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