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