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