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