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