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