001package jmri.jmrit.operations.trains;
002
003import java.awt.Dimension;
004import java.beans.PropertyChangeListener;
005import java.io.File;
006import java.io.PrintWriter;
007import java.util.*;
008
009import javax.swing.JComboBox;
010
011import org.jdom2.Attribute;
012import org.jdom2.Element;
013
014import jmri.*;
015import jmri.beans.PropertyChangeSupport;
016import jmri.jmrit.operations.OperationsPanel;
017import jmri.jmrit.operations.locations.Location;
018import jmri.jmrit.operations.rollingstock.cars.*;
019import jmri.jmrit.operations.rollingstock.engines.EngineManagerXml;
020import jmri.jmrit.operations.routes.Route;
021import jmri.jmrit.operations.routes.RouteLocation;
022import jmri.jmrit.operations.setup.OperationsSetupXml;
023import jmri.jmrit.operations.setup.Setup;
024import jmri.jmrit.operations.trains.excel.TrainCustomManifest;
025import jmri.jmrit.operations.trains.excel.TrainCustomSwitchList;
026import jmri.jmrit.operations.trains.gui.TrainsTableFrame;
027import jmri.jmrit.operations.trains.schedules.TrainScheduleManager;
028import jmri.jmrit.operations.trains.trainbuilder.TrainCommon;
029import jmri.script.JmriScriptEngineManager;
030import jmri.util.ColorUtil;
031import jmri.util.swing.JmriJOptionPane;
032
033/**
034 * Manages trains.
035 *
036 * @author Bob Jacobsen Copyright (C) 2003
037 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013,
038 *         2014
039 */
040public class TrainManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener {
041
042    protected static final String NONE = "";
043
044    // Train frame attributes
045    private String _trainAction = TrainsTableFrame.MOVE; // Trains frame table button action
046    private boolean _buildMessages = true; // when true, show build messages
047    private boolean _buildReport = false; // when true, print/preview build reports
048    private boolean _printPreview = false; // when true, preview train manifest
049    private boolean _openFile = false; // when true, open CSV file manifest
050    private boolean _runFile = false; // when true, run CSV file manifest
051
052    // Conductor attributes
053    private boolean _showLocationHyphenName = false;
054
055    // Trains window row colors
056    private boolean _rowColorManual = true; // when true train colors are manually assigned
057    private String _rowColorBuilt = NONE; // row color when train is built
058    private String _rowColorBuildFailed = NONE; // row color when train build failed
059    private String _rowColorTrainEnRoute = NONE; // row color when train is en route
060    private String _rowColorTerminated = NONE; // row color when train is terminated
061    private String _rowColorReset = NONE; // row color when train is reset
062
063    // Scripts
064    protected List<String> _startUpScripts = new ArrayList<>(); // list of script pathnames to run at start up
065    protected List<String> _shutDownScripts = new ArrayList<>(); // list of script pathnames to run at shut down
066
067    // property changes
068    public static final String LISTLENGTH_CHANGED_PROPERTY = "TrainsListLength"; // NOI18N
069    public static final String PRINTPREVIEW_CHANGED_PROPERTY = "TrainsPrintPreview"; // NOI18N
070    public static final String OPEN_FILE_CHANGED_PROPERTY = "TrainsOpenFile"; // NOI18N
071    public static final String RUN_FILE_CHANGED_PROPERTY = "TrainsRunFile"; // NOI18N
072    public static final String TRAIN_ACTION_CHANGED_PROPERTY = "TrainsAction"; // NOI18N
073    public static final String ROW_COLOR_NAME_CHANGED_PROPERTY = "TrainsRowColorChange"; // NOI18N
074    public static final String TRAINS_BUILT_CHANGED_PROPERTY = "TrainsBuiltChange"; // NOI18N
075    public static final String TRAINS_SHOW_FULL_NAME_PROPERTY = "TrainsShowFullName"; // NOI18N
076    public static final String TRAINS_SAVED_PROPERTY = "TrainsSaved"; // NOI18N
077
078    public TrainManager() {
079    }
080
081    private int _id = 0; // train ids
082
083    /**
084     * Get the number of items in the roster
085     *
086     * @return Number of trains in the roster
087     */
088    public int getNumEntries() {
089        return _trainHashTable.size();
090    }
091
092    /**
093     * @return true if build messages are enabled
094     */
095    public boolean isBuildMessagesEnabled() {
096        return _buildMessages;
097    }
098
099    public void setBuildMessagesEnabled(boolean enable) {
100        boolean old = _buildMessages;
101        _buildMessages = enable;
102        setDirtyAndFirePropertyChange("BuildMessagesEnabled", enable, old); // NOI18N
103    }
104
105    /**
106     * @return true if build reports are enabled
107     */
108    public boolean isBuildReportEnabled() {
109        return _buildReport;
110    }
111
112    public void setBuildReportEnabled(boolean enable) {
113        boolean old = _buildReport;
114        _buildReport = enable;
115        setDirtyAndFirePropertyChange("BuildReportEnabled", enable, old); // NOI18N
116    }
117
118    /**
119     * @return true if open file is enabled
120     */
121    public boolean isOpenFileEnabled() {
122        return _openFile;
123    }
124
125    public void setOpenFileEnabled(boolean enable) {
126        boolean old = _openFile;
127        _openFile = enable;
128        setDirtyAndFirePropertyChange(OPEN_FILE_CHANGED_PROPERTY, old, enable);
129    }
130
131    /**
132     * @return true if open file is enabled
133     */
134    public boolean isRunFileEnabled() {
135        return _runFile;
136    }
137
138    public void setRunFileEnabled(boolean enable) {
139        boolean old = _runFile;
140        _runFile = enable;
141        setDirtyAndFirePropertyChange(RUN_FILE_CHANGED_PROPERTY, old, enable);
142    }
143
144    /**
145     * @return true if print preview is enabled
146     */
147    public boolean isPrintPreviewEnabled() {
148        return _printPreview;
149    }
150
151    public void setPrintPreviewEnabled(boolean enable) {
152        boolean old = _printPreview;
153        _printPreview = enable;
154        setDirtyAndFirePropertyChange(PRINTPREVIEW_CHANGED_PROPERTY, old ? "Preview" : "Print", // NOI18N
155                enable ? "Preview" : "Print"); // NOI18N
156    }
157
158    /**
159     * When true show entire location name including hyphen
160     * 
161     * @return true when showing entire location name
162     */
163    public boolean isShowLocationHyphenNameEnabled() {
164        return _showLocationHyphenName;
165    }
166
167    public void setShowLocationHyphenNameEnabled(boolean enable) {
168        boolean old = _showLocationHyphenName;
169        _showLocationHyphenName = enable;
170        setDirtyAndFirePropertyChange(TRAINS_SHOW_FULL_NAME_PROPERTY, old, enable);
171    }
172
173    public String getTrainsFrameTrainAction() {
174        return _trainAction;
175    }
176
177    public void setTrainsFrameTrainAction(String action) {
178        String old = _trainAction;
179        _trainAction = action;
180        if (!old.equals(action)) {
181            setDirtyAndFirePropertyChange(TRAIN_ACTION_CHANGED_PROPERTY, old, action);
182        }
183    }
184
185    /**
186     * Add a script to run after trains have been loaded
187     *
188     * @param pathname The script's pathname
189     */
190    public void addStartUpScript(String pathname) {
191        _startUpScripts.add(pathname);
192        setDirtyAndFirePropertyChange("addStartUpScript", pathname, null); // NOI18N
193    }
194
195    public void deleteStartUpScript(String pathname) {
196        _startUpScripts.remove(pathname);
197        setDirtyAndFirePropertyChange("deleteStartUpScript", null, pathname); // NOI18N
198    }
199
200    /**
201     * Gets a list of pathnames to run after trains have been loaded
202     *
203     * @return A list of pathnames to run after trains have been loaded
204     */
205    public List<String> getStartUpScripts() {
206        return _startUpScripts;
207    }
208
209    public void runStartUpScripts() {
210        // use thread to prevent object (Train) thread lock
211        Thread scripts = jmri.util.ThreadingUtil.newThread(new Runnable() {
212            @Override
213            public void run() {
214                for (String scriptPathName : getStartUpScripts()) {
215                    try {
216                        JmriScriptEngineManager.getDefault()
217                                .runScript(new File(jmri.util.FileUtil.getExternalFilename(scriptPathName)));
218                    } catch (Exception e) {
219                        log.error("Problem with script: {}", scriptPathName);
220                    }
221                }
222            }
223        });
224        scripts.setName("Startup Scripts"); // NOI18N
225        scripts.start();
226    }
227
228    /**
229     * Add a script to run at shutdown
230     *
231     * @param pathname The script's pathname
232     */
233    public void addShutDownScript(String pathname) {
234        _shutDownScripts.add(pathname);
235        setDirtyAndFirePropertyChange("addShutDownScript", pathname, null); // NOI18N
236    }
237
238    public void deleteShutDownScript(String pathname) {
239        _shutDownScripts.remove(pathname);
240        setDirtyAndFirePropertyChange("deleteShutDownScript", null, pathname); // NOI18N
241    }
242
243    /**
244     * Gets a list of pathnames to run at shutdown
245     *
246     * @return A list of pathnames to run at shutdown
247     */
248    public List<String> getShutDownScripts() {
249        return _shutDownScripts;
250    }
251
252    public void runShutDownScripts() {
253        for (String scriptPathName : getShutDownScripts()) {
254            try {
255                JmriScriptEngineManager.getDefault()
256                        .runScript(new File(jmri.util.FileUtil.getExternalFilename(scriptPathName)));
257            } catch (Exception e) {
258                log.error("Problem with script: {}", scriptPathName);
259            }
260        }
261    }
262
263    /**
264     * Used to determine if a train has any restrictions with regard to car
265     * built dates.
266     * 
267     * @return true if there's a restriction
268     */
269    public boolean isBuiltRestricted() {
270        for (Train train : getList()) {
271            if (!train.getBuiltStartYear().equals(Train.NONE) || !train.getBuiltEndYear().equals(Train.NONE)) {
272                return true;
273            }
274        }
275        return false;
276    }
277
278    /**
279     * Used to determine if a train has any restrictions with regard to car
280     * loads.
281     * 
282     * @return true if there's a restriction
283     */
284    public boolean isLoadRestricted() {
285        for (Train train : getList()) {
286            if (!train.getLoadOption().equals(Train.ALL_LOADS)) {
287                return true;
288            }
289        }
290        return false;
291    }
292
293    /**
294     * Used to determine if a train has any restrictions with regard to car
295     * roads.
296     * 
297     * @return true if there's a restriction
298     */
299    public boolean isCarRoadRestricted() {
300        for (Train train : getList()) {
301            if (!train.getCarRoadOption().equals(Train.ALL_ROADS)) {
302                return true;
303            }
304        }
305        return false;
306    }
307
308    /**
309     * Used to determine if a train has any restrictions with regard to caboose
310     * roads.
311     * 
312     * @return true if there's a restriction
313     */
314    public boolean isCabooseRoadRestricted() {
315        for (Train train : getList()) {
316            if (!train.getCabooseRoadOption().equals(Train.ALL_ROADS)) {
317                return true;
318            }
319        }
320        return false;
321    }
322
323    /**
324     * Used to determine if a train has any restrictions with regard to
325     * Locomotive roads.
326     * 
327     * @return true if there's a restriction
328     */
329    public boolean isLocoRoadRestricted() {
330        for (Train train : getList()) {
331            if (!train.getLocoRoadOption().equals(Train.ALL_ROADS)) {
332                return true;
333            }
334        }
335        return false;
336    }
337
338    /**
339     * Used to determine if a train has any restrictions with regard to car
340     * owners.
341     * 
342     * @return true if there's a restriction
343     */
344    public boolean isOwnerRestricted() {
345        for (Train train : getList()) {
346            if (!train.getOwnerOption().equals(Train.ALL_OWNERS)) {
347                return true;
348            }
349        }
350        return false;
351    }
352
353    public void dispose() {
354        _trainHashTable.clear();
355        _id = 0;
356    }
357
358    // stores known Train instances by id
359    private final Hashtable<String, Train> _trainHashTable = new Hashtable<>();
360
361    /**
362     * @param name The train's name.
363     * @return requested Train object or null if none exists
364     */
365    public Train getTrainByName(String name) {
366        if (!InstanceManager.getDefault(TrainManagerXml.class).isTrainFileLoaded()) {
367            log.error("TrainManager getTrainByName called before trains completely loaded!");
368        }
369        Train train;
370        Enumeration<Train> en = _trainHashTable.elements();
371        while (en.hasMoreElements()) {
372            train = en.nextElement();
373            // windows file names are case independent
374            if (train.getName().toLowerCase().equals(name.toLowerCase())) {
375                return train;
376            }
377        }
378        log.debug("Train ({}) doesn't exist", name);
379        return null;
380    }
381
382    public Train getTrainById(String id) {
383        if (!InstanceManager.getDefault(TrainManagerXml.class).isTrainFileLoaded()) {
384            log.error("TrainManager getTrainById called before trains completely loaded!");
385        }
386        return _trainHashTable.get(id);
387    }
388
389    /**
390     * Finds an existing train or creates a new train if needed. Requires
391     * train's name and creates a unique id for a new train
392     *
393     * @param name The train's name.
394     * @return new train or existing train
395     */
396    public Train newTrain(String name) {
397        Train train = getTrainByName(name);
398        if (train == null) {
399            _id++;
400            train = new Train(Integer.toString(_id), name);
401            int oldSize = getNumEntries();
402            _trainHashTable.put(train.getId(), train);
403            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, getNumEntries());
404        }
405        return train;
406    }
407
408    /**
409     * Remember a NamedBean Object created outside the manager.
410     *
411     * @param train The Train to be added.
412     */
413    public void register(Train train) {
414        int oldSize = getNumEntries();
415        _trainHashTable.put(train.getId(), train);
416        // find last id created
417        int id = Integer.parseInt(train.getId());
418        if (id > _id) {
419            _id = id;
420        }
421        train.addPropertyChangeListener(this);
422        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, getNumEntries());
423    }
424
425    /**
426     * Forget a NamedBean Object created outside the manager.
427     *
428     * @param train The Train to delete.
429     */
430    public void deregister(Train train) {
431        if (train == null) {
432            return;
433        }
434        train.dispose();
435        int oldSize = getNumEntries();
436        _trainHashTable.remove(train.getId());
437        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, getNumEntries());
438    }
439
440    public void replaceLoad(String type, String oldLoadName, String newLoadName) {
441        for (Train train : getList()) {
442            for (String loadName : train.getLoadNames()) {
443                if (loadName.equals(oldLoadName)) {
444                    train.deleteLoadName(oldLoadName);
445                    if (newLoadName != null) {
446                        train.addLoadName(newLoadName);
447                    }
448                }
449                // adjust combination car type and load name
450                String[] splitLoad = loadName.split(CarLoad.SPLIT_CHAR);
451                if (splitLoad.length > 1) {
452                    if (splitLoad[0].equals(type) && splitLoad[1].equals(oldLoadName)) {
453                        train.deleteLoadName(loadName);
454                        if (newLoadName != null) {
455                            train.addLoadName(type + CarLoad.SPLIT_CHAR + newLoadName);
456                        }
457                    }
458                }
459            }
460        }
461    }
462
463    /**
464     * @return true if there's a built train
465     */
466    public boolean isAnyTrainBuilt() {
467        for (Train train : getList()) {
468            if (train.isBuilt()) {
469                return true;
470            }
471        }
472        return false;
473    }
474
475    /**
476     * @return true if there's a train being built
477     */
478    public boolean isAnyTrainBuilding() {
479        if (getTrainBuilding() != null) {
480            return true;
481        }
482        return false;
483    }
484
485    public Train getTrainBuilding() {
486        for (Train train : getList()) {
487            if (train.isBuilding()) {
488                log.debug("Train {} is currently building", train.getName());
489                return train;
490            }
491        }
492        return null;
493    }
494
495    /**
496     * Gets the last train built by departure time.
497     * 
498     * @return last train built by departure time, or null if no trains are
499     *         built.
500     */
501    public Train getLastTrainBuiltByDepartureTime() {
502        for (Train train : getTrainsByReverseTimeList()) {
503            if (train.isBuilt() && train.getDepartTimeMinutes() > 0) {
504                return train;
505            }
506        }
507        return null;
508    }
509    
510    /**
511     * Used to determine if there's a train build after the train in question.
512     * @param train the train to be checked
513     * @return null or a train built after the train in question.
514     */
515    public Train getTrainBuiltAfter(Train train) {
516        List<Train> trains = getTrainsByReverseTimeList();
517        for (Train t : trains) {
518            if (train == t || train.getDepartTimeMinutes() == t.getDepartTimeMinutes()) {
519                break;
520            }
521            if (t.isBuilt()) {
522                return t;
523            }
524        }
525        return null;
526    }
527
528    /**
529     * @param car         The car looking for a train.
530     * @param buildReport The optional build report for logging.
531     * @return Train that can service car from its current location to the its
532     *         destination.
533     */
534    public Train getTrainForCar(Car car, PrintWriter buildReport) {
535        return getTrainForCar(car, new ArrayList<>(), buildReport, false);
536    }
537
538    /**
539     * @param car             The car looking for a train.
540     * @param excludeTrains   The trains not to try.
541     * @param buildReport     The optional build report for logging.
542     * @param isExcludeRoutes When true eliminate trains that have the same
543     *                        route in the exclude trains list.
544     * @return Train that can service car from its current location to the its
545     *         destination.
546     */
547    public Train getTrainForCar(Car car, List<Train> excludeTrains, PrintWriter buildReport, boolean isExcludeRoutes) {
548        addLine(buildReport, TrainCommon.BLANK_LINE);
549        addLine(buildReport, Bundle.getMessage("trainFindForCar", car.toString(), car.getLocationName(),
550                car.getTrackName(), car.getDestinationName(), car.getDestinationTrackName()));
551
552        main: for (Train train : getTrainsByNameList()) {
553            if (excludeTrains.contains(train)) {
554                continue;
555            }
556            if (Setup.isOnlyActiveTrainsEnabled() && !train.isBuildEnabled()) {
557                continue;
558            }
559            if (isExcludeRoutes) {
560                for (Train t : excludeTrains) {
561                    if (t != null && train.getRoute() == t.getRoute()) {
562                        addLine(buildReport, Bundle.getMessage("trainHasSameRoute", train, t));
563                        continue main;
564                    }
565                }
566            }
567            // does this train service this car?
568            if (train.isServiceable(buildReport, car)) {
569                log.debug("Found train ({}) for car ({}) location ({}, {}) destination ({}, {})", train.getName(),
570                        car.toString(), car.getLocationName(), car.getTrackName(), car.getDestinationName(),
571                        car.getDestinationTrackName()); // NOI18N
572                return train;
573            }
574        }
575        return null;
576    }
577
578    public List<Train> getExcludeTrainListForCar(Car car, PrintWriter buildReport) {
579        List<Train> excludeTrains = new ArrayList<>();
580        for (Train train : getTrainsByNameList()) {
581            if (Setup.isOnlyActiveTrainsEnabled() && !train.isBuildEnabled()) {
582                addLine(buildReport, Bundle.getMessage("trainRoutingDisabled", train.getName()));
583                excludeTrains.add(train);
584            } else if (!train.isTrainAbleToService(buildReport, car)) {
585                excludeTrains.add(train);
586            }
587        }
588        return excludeTrains;
589    }
590
591    protected static final String SEVEN = Setup.BUILD_REPORT_VERY_DETAILED;
592
593    private void addLine(PrintWriter buildReport, String string) {
594        if (Setup.getRouterBuildReportLevel().equals(SEVEN)) {
595            TrainCommon.addLine(buildReport, SEVEN, string);
596        }
597    }
598
599    /**
600     * Sort by train name
601     *
602     * @return list of trains ordered by name
603     */
604    public List<Train> getTrainsByNameList() {
605        return getTrainsByList(getList(), GET_TRAIN_NAME);
606    }
607
608    /**
609     * Sort by train departure time
610     *
611     * @return list of trains ordered by departure time
612     */
613    public List<Train> getTrainsByTimeList() {
614        return getTrainsByIntList(getTrainsByNameList(), GET_TRAIN_TIME);
615    }
616
617    public List<Train> getTrainsByReverseTimeList() {
618        List<Train> out = getTrainsByTimeList();
619        Collections.reverse(out);
620        return out;
621    }
622
623    /**
624     * Sort by train departure location name
625     *
626     * @return list of trains ordered by departure name
627     */
628    public List<Train> getTrainsByDepartureList() {
629        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_DEPARTES_NAME);
630    }
631
632    /**
633     * Sort by train termination location name
634     *
635     * @return list of trains ordered by termination name
636     */
637    public List<Train> getTrainsByTerminatesList() {
638        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_TERMINATES_NAME);
639    }
640
641    /**
642     * Sort by train route name
643     *
644     * @return list of trains ordered by route name
645     */
646    public List<Train> getTrainsByRouteList() {
647        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_ROUTE_NAME);
648    }
649
650    /**
651     * Sort by train status
652     *
653     * @return list of trains ordered by status
654     */
655    public List<Train> getTrainsByStatusList() {
656        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_STATUS);
657    }
658
659    /**
660     * Sort by train description
661     *
662     * @return list of trains ordered by train description
663     */
664    public List<Train> getTrainsByDescriptionList() {
665        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_DESCRIPTION);
666    }
667
668    /**
669     * Sort by train id
670     *
671     * @return list of trains ordered by id
672     */
673    public List<Train> getTrainsByIdList() {
674        return getTrainsByIntList(getList(), GET_TRAIN_ID);
675    }
676
677    private List<Train> getTrainsByList(List<Train> sortList, int attribute) {
678        List<Train> out = new ArrayList<>();
679        for (Train train : sortList) {
680            String trainAttribute = (String) getTrainAttribute(train, attribute);
681            for (int j = 0; j < out.size(); j++) {
682                if (trainAttribute.compareToIgnoreCase((String) getTrainAttribute(out.get(j), attribute)) < 0) {
683                    out.add(j, train);
684                    break;
685                }
686            }
687            if (!out.contains(train)) {
688                out.add(train);
689            }
690        }
691        return out;
692    }
693
694    private List<Train> getTrainsByIntList(List<Train> sortList, int attribute) {
695        List<Train> out = new ArrayList<>();
696        for (Train train : sortList) {
697            int trainAttribute = (Integer) getTrainAttribute(train, attribute);
698            for (int j = 0; j < out.size(); j++) {
699                if (trainAttribute < (Integer) getTrainAttribute(out.get(j), attribute)) {
700                    out.add(j, train);
701                    break;
702                }
703            }
704            if (!out.contains(train)) {
705                out.add(train);
706            }
707        }
708        return out;
709    }
710
711    // the various sort options for trains
712    private static final int GET_TRAIN_DEPARTES_NAME = 0;
713    private static final int GET_TRAIN_NAME = 1;
714    private static final int GET_TRAIN_ROUTE_NAME = 2;
715    private static final int GET_TRAIN_TERMINATES_NAME = 3;
716    private static final int GET_TRAIN_TIME = 4;
717    private static final int GET_TRAIN_STATUS = 5;
718    private static final int GET_TRAIN_ID = 6;
719    private static final int GET_TRAIN_DESCRIPTION = 7;
720
721    private Object getTrainAttribute(Train train, int attribute) {
722        switch (attribute) {
723            case GET_TRAIN_DEPARTES_NAME:
724                return train.getTrainDepartsName();
725            case GET_TRAIN_NAME:
726                return train.getName();
727            case GET_TRAIN_ROUTE_NAME:
728                return train.getTrainRouteName();
729            case GET_TRAIN_TERMINATES_NAME:
730                return train.getTrainTerminatesName();
731            case GET_TRAIN_TIME:
732                return train.getDepartTimeMinutes();
733            case GET_TRAIN_STATUS:
734                return train.getStatus();
735            case GET_TRAIN_ID:
736                return Integer.parseInt(train.getId());
737            case GET_TRAIN_DESCRIPTION:
738                return train.getDescription();
739            default:
740                return "unknown"; // NOI18N
741        }
742    }
743
744    public List<Train> getList() {
745        if (!InstanceManager.getDefault(TrainManagerXml.class).isTrainFileLoaded()) {
746            log.error("TrainManager getList called before trains completely loaded!");
747        }
748        List<Train> out = new ArrayList<>();
749        Enumeration<Train> en = _trainHashTable.elements();
750        while (en.hasMoreElements()) {
751            out.add(en.nextElement());
752        }
753        return out;
754    }
755
756    public JComboBox<Train> getTrainComboBox() {
757        JComboBox<Train> box = new JComboBox<>();
758        updateTrainComboBox(box);
759        OperationsPanel.padComboBox(box);
760        return box;
761    }
762
763    public void updateTrainComboBox(JComboBox<Train> box) {
764        box.removeAllItems();
765        box.addItem(null);
766        for (Train train : getTrainsByNameList()) {
767            box.addItem(train);
768        }
769    }
770
771    /**
772     * Update combo box with trains that will service this car
773     *
774     * @param box the combo box to update
775     * @param car the car to be serviced
776     */
777    public void updateTrainComboBox(JComboBox<Train> box, Car car) {
778        box.removeAllItems();
779        box.addItem(null);
780        for (Train train : getTrainsByNameList()) {
781            if (train.isServiceable(car)) {
782                box.addItem(train);
783            }
784        }
785    }
786
787    public boolean isRowColorManual() {
788        return _rowColorManual;
789    }
790
791    public void setRowColorsManual(boolean manual) {
792        boolean old = _rowColorManual;
793        _rowColorManual = manual;
794        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, manual);
795    }
796
797    public String getRowColorNameForBuilt() {
798        return _rowColorBuilt;
799    }
800
801    public void setRowColorNameForBuilt(String colorName) {
802        String old = _rowColorBuilt;
803        _rowColorBuilt = colorName;
804        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
805    }
806
807    public String getRowColorNameForBuildFailed() {
808        return _rowColorBuildFailed;
809    }
810
811    public void setRowColorNameForBuildFailed(String colorName) {
812        String old = _rowColorBuildFailed;
813        _rowColorBuildFailed = colorName;
814        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
815    }
816
817    public String getRowColorNameForTrainEnRoute() {
818        return _rowColorTrainEnRoute;
819    }
820
821    public void setRowColorNameForTrainEnRoute(String colorName) {
822        String old = _rowColorTrainEnRoute;
823        _rowColorTrainEnRoute = colorName;
824        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
825    }
826
827    public String getRowColorNameForTerminated() {
828        return _rowColorTerminated;
829    }
830
831    public void setRowColorNameForTerminated(String colorName) {
832        String old = _rowColorTerminated;
833        _rowColorTerminated = colorName;
834        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
835    }
836
837    public String getRowColorNameForReset() {
838        return _rowColorReset;
839    }
840
841    public void setRowColorNameForReset(String colorName) {
842        String old = _rowColorReset;
843        _rowColorReset = colorName;
844        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
845    }
846
847    /**
848     * JColorChooser is not a replacement for getRowColorComboBox as it doesn't
849     * support no color as a selection.
850     * 
851     * @return the available colors used highlighting table rows including no
852     *         color.
853     */
854    public JComboBox<String> getRowColorComboBox() {
855        JComboBox<String> box = new JComboBox<>();
856        box.addItem(NONE);
857        box.addItem(ColorUtil.ColorBlack);
858        box.addItem(ColorUtil.ColorRed);
859        box.addItem(ColorUtil.ColorPink);
860        box.addItem(ColorUtil.ColorOrange);
861        box.addItem(ColorUtil.ColorYellow);
862        box.addItem(ColorUtil.ColorGreen);
863        box.addItem(ColorUtil.ColorMagenta);
864        box.addItem(ColorUtil.ColorCyan);
865        box.addItem(ColorUtil.ColorBlue);
866        box.addItem(ColorUtil.ColorGray);
867        return box;
868    }
869
870    /**
871     * Makes a copy of an existing train.
872     *
873     * @param train     the train to copy
874     * @param trainName the name of the new train
875     * @return a copy of train
876     */
877    public Train copyTrain(Train train, String trainName) {
878        Train newTrain = newTrain(trainName);
879        // route, departure time and types
880        newTrain.setRoute(train.getRoute());
881        newTrain.setTrainSkipsLocations(train.getTrainSkipsLocations());
882        newTrain.setDepartureTime(train.getDepartureTimeDay(), train.getDepartureTimeHour(),
883                train.getDepartureTimeMinute());
884        newTrain._typeList.clear(); // remove all types loaded by create
885        newTrain.setTypeNames(train.getTypeNames());
886        // set road, load, and owner options
887        newTrain.setCarRoadOption(train.getCarRoadOption());
888        newTrain.setCarRoadNames(train.getCarRoadNames());
889        newTrain.setCabooseRoadNames(train.getCabooseRoadNames());
890        newTrain.setLocoRoadOption(train.getLocoRoadOption());
891        newTrain.setLocoRoadNames(train.getLocoRoadNames());
892        newTrain.setLoadOption(train.getLoadOption());
893        newTrain.setLoadNames(train.getLoadNames());
894        newTrain.setOwnerOption(train.getOwnerOption());
895        newTrain.setOwnerNames(train.getOwnerNames());
896        // build dates
897        newTrain.setBuiltStartYear(train.getBuiltStartYear());
898        newTrain.setBuiltEndYear(train.getBuiltEndYear());
899        // locos start of route
900        newTrain.setNumberEngines(train.getNumberEngines());
901        newTrain.setEngineModel(train.getEngineModel());
902        newTrain.setEngineRoad(train.getEngineRoad());
903        newTrain.setRequirements(train.getRequirements());
904        newTrain.setCabooseRoad(train.getCabooseRoad());
905        // second leg
906        newTrain.setSecondLegNumberEngines(train.getSecondLegNumberEngines());
907        newTrain.setSecondLegEngineModel(train.getSecondLegEngineModel());
908        newTrain.setSecondLegEngineRoad(train.getSecondLegEngineRoad());
909        newTrain.setSecondLegOptions(train.getSecondLegOptions());
910        newTrain.setSecondLegCabooseRoad(train.getSecondLegCabooseRoad());
911        newTrain.setSecondLegStartRouteLocation(train.getSecondLegStartRouteLocation());
912        newTrain.setSecondLegEndRouteLocation(train.getSecondLegEndRouteLocation());
913        // third leg
914        newTrain.setThirdLegNumberEngines(train.getThirdLegNumberEngines());
915        newTrain.setThirdLegEngineModel(train.getThirdLegEngineModel());
916        newTrain.setThirdLegEngineRoad(train.getThirdLegEngineRoad());
917        newTrain.setThirdLegOptions(train.getThirdLegOptions());
918        newTrain.setThirdLegCabooseRoad(train.getThirdLegCabooseRoad());
919        newTrain.setThirdLegStartRouteLocation(train.getThirdLegStartRouteLocation());
920        newTrain.setThirdLegEndRouteLocation(train.getThirdLegEndRouteLocation());
921        // scripts
922        for (String scriptName : train.getBuildScripts()) {
923            newTrain.addBuildScript(scriptName);
924        }
925        for (String scriptName : train.getMoveScripts()) {
926            newTrain.addMoveScript(scriptName);
927        }
928        for (String scriptName : train.getTerminationScripts()) {
929            newTrain.addTerminationScript(scriptName);
930        }
931        // manifest options
932        newTrain.setRailroadName(train.getRailroadName());
933        newTrain.setManifestLogoPathName(train.getManifestLogoPathName());
934        newTrain.setShowArrivalAndDepartureTimes(train.isShowArrivalAndDepartureTimesEnabled());
935        // build options
936        newTrain.setAllowLocalMovesEnabled(train.isAllowLocalMovesEnabled());
937        newTrain.setAllowReturnToStagingEnabled(train.isAllowReturnToStagingEnabled());
938        newTrain.setAllowThroughCarsEnabled(train.isAllowThroughCarsEnabled());
939        newTrain.setBuildConsistEnabled(train.isBuildConsistEnabled());
940        newTrain.setSendCarsWithCustomLoadsToStagingEnabled(train.isSendCarsWithCustomLoadsToStagingEnabled());
941        newTrain.setBuildTrainNormalEnabled(train.isBuildTrainNormalEnabled());
942        newTrain.setSendCarsToTerminalEnabled(train.isSendCarsToTerminalEnabled());
943        newTrain.setServiceAllCarsWithFinalDestinationsEnabled(train.isServiceAllCarsWithFinalDestinationsEnabled());
944        // comment
945        newTrain.setComment(train.getCommentWithColor());
946        // description
947        newTrain.setDescription(train.getRawDescription());
948        return newTrain;
949    }
950
951    /**
952     * Provides a list of trains ordered by arrival time to a location. The list
953     * can contain a train multiple times if the train also services the
954     * location more than once.
955     *
956     * @param location The location
957     * @return A list of trains ordered by arrival time.
958     */
959    public List<Train> getTrainsArrivingThisLocationList(Location location) {
960        return getTrainsArrivingThisLocationList(location, false);
961    }
962
963    public List<Train> getTrainsArrivingThisLocationList(Location location, boolean multiple) {
964        // get a list of trains
965        List<Train> out = new ArrayList<>();
966        List<Integer> arrivalTimes = new ArrayList<>();
967        for (Train train : getTrainsByTimeList()) {
968            if (!train.isBuilt()) {
969                continue; // train wasn't built so skip
970            }
971            Route route = train.getRoute();
972            if (route == null) {
973                continue; // no route for this train
974            }
975            RouteLocation rlPrevious = null;
976            for (RouteLocation rl : route.getLocationsBySequenceList()) {
977                if (rlPrevious != null &&
978                        rl.getLocation().getSplitName().equals(rlPrevious.getLocation().getSplitName())) {
979                    continue;
980                }
981                // ignore back to back location with the same name
982                rlPrevious = rl;
983                if (rl.getSplitName().equals(location.getSplitName())) {
984                    boolean trainAdded = false;
985                    int expectedArrivalTime = train.getExpectedTravelTimeInMinutes(rl);
986                    // is already serviced then -1
987                    if (expectedArrivalTime == Train.SERVICED) {
988                        out.add(0, train); // place all trains that have already been serviced at the start
989                        arrivalTimes.add(0, expectedArrivalTime);
990                        trainAdded = true;
991                    } // if the train is in route, then expected arrival time is in minutes
992                    else if (train.isTrainEnRoute()) {
993                        for (int j = 0; j < out.size(); j++) {
994                            Train t = out.get(j);
995                            int time = arrivalTimes.get(j);
996                            if (t.isTrainEnRoute() && expectedArrivalTime < time) {
997                                out.add(j, train);
998                                arrivalTimes.add(j, expectedArrivalTime);
999                                trainAdded = true;
1000                                break;
1001                            }
1002                            if (!t.isTrainEnRoute()) {
1003                                out.add(j, train);
1004                                arrivalTimes.add(j, expectedArrivalTime);
1005                                trainAdded = true;
1006                                break;
1007                            }
1008                        }
1009                        // Train has not departed
1010                    } else {
1011                        for (int j = 0; j < out.size(); j++) {
1012                            Train t = out.get(j);
1013                            int time = arrivalTimes.get(j);
1014                            if (!t.isTrainEnRoute() && expectedArrivalTime < time) {
1015                                out.add(j, train);
1016                                arrivalTimes.add(j, expectedArrivalTime);
1017                                trainAdded = true;
1018                                break;
1019                            }
1020                        }
1021                    }
1022                    if (!trainAdded) {
1023                        out.add(train);
1024                        arrivalTimes.add(expectedArrivalTime);
1025                    }
1026                    if (!multiple) {
1027                        break; // done
1028                    }
1029                }
1030            }
1031        }
1032        return out;
1033    }
1034
1035    /**
1036     * Loads train icons if needed
1037     */
1038    public void loadTrainIcons() {
1039        for (Train train : getTrainsByIdList()) {
1040            train.loadTrainIcon();
1041        }
1042    }
1043
1044    /**
1045     * Sets the switch list status for all built trains. Used for switch lists
1046     * in consolidated mode.
1047     *
1048     * @param status Train.PRINTED, Train.UNKNOWN
1049     */
1050    public void setTrainsSwitchListStatus(String status) {
1051        for (Train train : getTrainsByTimeList()) {
1052            if (!train.isBuilt()) {
1053                continue; // train isn't built so skip
1054            }
1055            train.setSwitchListStatus(status);
1056        }
1057    }
1058
1059    /**
1060     * Sets all built trains manifests to modified. This causes the train's
1061     * manifest to be recreated.
1062     */
1063    public void setTrainsModified() {
1064        for (Train train : getTrainsByTimeList()) {
1065            if (!train.isBuilt() || train.isTrainEnRoute()) {
1066                continue; // train wasn't built or in route, so skip
1067            }
1068            train.setModified(true);
1069        }
1070    }
1071
1072    public void buildSelectedTrains(List<Train> trains) {
1073        // use a thread to allow table updates during build
1074        Thread build = jmri.util.ThreadingUtil.newThread(new Runnable() {
1075            @Override
1076            public void run() {
1077                for (Train train : trains) {
1078                    if (train.buildIfSelected()) {
1079                        continue;
1080                    }
1081                    if (isBuildMessagesEnabled() && train.isBuildEnabled() && !train.isBuilt()) {
1082                        if (JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("ContinueBuilding"),
1083                                Bundle.getMessage("buildFailedMsg",
1084                                        train.getName()),
1085                                JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.NO_OPTION) {
1086                            break;
1087                        }
1088                    }
1089                }
1090                setDirtyAndFirePropertyChange(TRAINS_BUILT_CHANGED_PROPERTY, false, true);
1091            }
1092        });
1093        build.setName("Build Trains"); // NOI18N
1094        build.start();
1095    }
1096
1097    /**
1098     * Checks to see if using on time build mode and the train to be built has a
1099     * departure time equal to or after all of the other built trains.
1100     * 
1101     * @param train the train wanting to be built
1102     * @return true if okay to build train
1103     */
1104    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_FORMAT_SHOULD_BE_CONST",
1105            justification = "I18N of warning message")
1106    public boolean checkBuildOrder(Train train) {
1107        if (Setup.isBuildOnTime()) {
1108            Train t = getLastTrainBuiltByDepartureTime();
1109            if (t != null && train.getDepartTimeMinutes() < t.getDepartTimeMinutes()) {
1110                if (isBuildMessagesEnabled()) {
1111                    JmriJOptionPane.showMessageDialog(null,
1112                            Bundle.getMessage("TrainBuildTimeError", train.getName(), train.getDepartureTime(),
1113                                    t.getName(), t.getDepartureTime()),
1114                            Bundle.getMessage("TrainBuildTime"), JmriJOptionPane.ERROR_MESSAGE);
1115                } else {
1116                    log.error(Bundle.getMessage("TrainBuildTimeError", train.getName(), train.getDepartureTime(),
1117                            t.getName(), t.getDepartureTime()));
1118                }
1119                return false;
1120            }
1121        }
1122        return true;
1123    }
1124
1125    public boolean printSelectedTrains(List<Train> trains) {
1126        boolean status = true;
1127        for (Train train : trains) {
1128            if (train.isBuildEnabled()) {
1129                if (train.printManifestIfBuilt()) {
1130                    continue;
1131                }
1132                status = false; // failed to print all selected trains
1133                if (isBuildMessagesEnabled()) {
1134                    int response = JmriJOptionPane.showConfirmDialog(null,
1135                            Bundle.getMessage("NeedToBuildBeforePrinting",
1136                                    train.getName(),
1137                                    (isPrintPreviewEnabled() ? Bundle.getMessage("preview")
1138                                            : Bundle.getMessage("print"))),
1139                            Bundle.getMessage("CanNotPrintManifest",
1140                                    isPrintPreviewEnabled() ? Bundle.getMessage("preview")
1141                                            : Bundle.getMessage("print")),
1142                            JmriJOptionPane.OK_CANCEL_OPTION);
1143                    if (response != JmriJOptionPane.OK_OPTION) {
1144                        break;
1145                    }
1146                }
1147            }
1148        }
1149        return status;
1150    }
1151
1152    public boolean terminateSelectedTrains(List<Train> trains) {
1153        if (!confirmTerminateTrains(trains)) {
1154            return false;
1155        }
1156        boolean status = true;
1157        for (Train train : trains) {
1158            if (train.isBuildEnabled() && train.isBuilt()) {
1159                if (train.isPrinted()) {
1160                    train.terminate();
1161                } else {
1162                    status = false;
1163                    int response = JmriJOptionPane.showConfirmDialog(null,
1164                            Bundle.getMessage("WarningTrainManifestNotPrinted"),
1165                            Bundle.getMessage("TerminateTrain",
1166                                    train.getName(), train.getDescription()),
1167                            JmriJOptionPane.YES_NO_CANCEL_OPTION);
1168                    if (response == JmriJOptionPane.YES_OPTION) {
1169                        train.terminate();
1170                    }
1171                    // else Quit?
1172                    if (response == JmriJOptionPane.CLOSED_OPTION || response == JmriJOptionPane.CANCEL_OPTION) {
1173                        break;
1174                    }
1175                }
1176            }
1177        }
1178        return status;
1179    }
1180
1181    private boolean confirmTerminateTrains(List<Train> trains) {
1182        if (isBuildMessagesEnabled()) {
1183            int count = 0;
1184            for (Train train : trains) {
1185                if (train.isBuildEnabled() && train.isBuilt()) {
1186                    count += 1;
1187                }
1188            }
1189            int response = JmriJOptionPane.showConfirmDialog(null,
1190                    Bundle.getMessage("ConfirmTerminate", count),
1191                    Bundle.getMessage("TerminateSelectedTip"),
1192                    JmriJOptionPane.YES_NO_OPTION);
1193            if (response == JmriJOptionPane.NO_OPTION) {
1194                return false;
1195            }
1196        }
1197        return true;
1198    }
1199
1200    public void resetTrains() {
1201        int response = JmriJOptionPane.showConfirmDialog(null,
1202                Bundle.getMessage("ConfirmReset"),
1203                Bundle.getMessage("ConfirmReset"),
1204                JmriJOptionPane.YES_NO_OPTION);
1205        if (response == JmriJOptionPane.YES_OPTION) {
1206            for (Train train : getTrainsByReverseTimeList()) {
1207                train.reset();
1208            }
1209        }
1210    }
1211
1212    public void resetBuildFailedTrains() {
1213        for (Train train : getList()) {
1214            if (train.isBuildFailed())
1215                train.reset();
1216        }
1217    }
1218
1219    int _maxTrainNameLength = 0;
1220
1221    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_FORMAT_SHOULD_BE_CONST",
1222            justification = "I18N of Info Message")
1223    public int getMaxTrainNameLength() {
1224        String trainName = "";
1225        if (_maxTrainNameLength == 0) {
1226            for (Train train : getList()) {
1227                if (train.getName().length() > _maxTrainNameLength) {
1228                    trainName = train.getName();
1229                    _maxTrainNameLength = train.getName().length();
1230                }
1231            }
1232            log.info(Bundle.getMessage("InfoMaxName", trainName, _maxTrainNameLength));
1233        }
1234        return _maxTrainNameLength;
1235    }
1236
1237    private final Hashtable<String, Integer> _HardcopyWriterHashTable = new Hashtable<>();
1238
1239    public Integer getHardcopyWriterLineLength(String fontName, Integer fontStyle, Integer fontsize, Dimension pagesize,
1240            boolean isLandscape) {
1241        return _HardcopyWriterHashTable.get(getHardcopyWriterKey(fontName, fontStyle, fontsize, pagesize, isLandscape));
1242    }
1243
1244    public void setHardcopyWriterLineLength(String fontName, Integer fontStyle, Integer fontsize, Dimension pagesize,
1245            boolean isLandscape, Integer charsPerLine) {
1246        _HardcopyWriterHashTable.put(getHardcopyWriterKey(fontName, fontStyle, fontsize, pagesize, isLandscape),
1247                charsPerLine);
1248    }
1249
1250    private String getHardcopyWriterKey(String fontName, Integer fontStyle, Integer fontsize, Dimension pagesize,
1251            boolean isLandscape) {
1252        return fontName + fontStyle + fontsize + pagesize.width + (isLandscape ? "L" : "P");
1253    }
1254
1255    public void load(Element root) {
1256        if (root.getChild(Xml.OPTIONS) != null) {
1257            Element options = root.getChild(Xml.OPTIONS);
1258            InstanceManager.getDefault(TrainCustomManifest.class).load(options);
1259            InstanceManager.getDefault(TrainCustomSwitchList.class).load(options);
1260            Element e = options.getChild(Xml.TRAIN_OPTIONS);
1261            Attribute a;
1262            if (e != null) {
1263                if ((a = e.getAttribute(Xml.BUILD_MESSAGES)) != null) {
1264                    _buildMessages = a.getValue().equals(Xml.TRUE);
1265                }
1266                if ((a = e.getAttribute(Xml.BUILD_REPORT)) != null) {
1267                    _buildReport = a.getValue().equals(Xml.TRUE);
1268                }
1269                if ((a = e.getAttribute(Xml.PRINT_PREVIEW)) != null) {
1270                    _printPreview = a.getValue().equals(Xml.TRUE);
1271                }
1272                if ((a = e.getAttribute(Xml.OPEN_FILE)) != null) {
1273                    _openFile = a.getValue().equals(Xml.TRUE);
1274                }
1275                if ((a = e.getAttribute(Xml.RUN_FILE)) != null) {
1276                    _runFile = a.getValue().equals(Xml.TRUE);
1277                }
1278                // verify that the Trains Window action is valid
1279                if ((a = e.getAttribute(Xml.TRAIN_ACTION)) != null &&
1280                        (a.getValue().equals(TrainsTableFrame.MOVE) ||
1281                                a.getValue().equals(TrainsTableFrame.RESET) ||
1282                                a.getValue().equals(TrainsTableFrame.TERMINATE) ||
1283                                a.getValue().equals(TrainsTableFrame.CONDUCTOR))) {
1284                    _trainAction = a.getValue();
1285                }
1286            }
1287
1288            // Conductor options
1289            Element eConductorOptions = options.getChild(Xml.CONDUCTOR_OPTIONS);
1290            if (eConductorOptions != null) {
1291                if ((a = eConductorOptions.getAttribute(Xml.SHOW_HYPHEN_NAME)) != null) {
1292                    _showLocationHyphenName = a.getValue().equals(Xml.TRUE);
1293                }
1294            }
1295
1296            // Row color options
1297            Element eRowColorOptions = options.getChild(Xml.ROW_COLOR_OPTIONS);
1298            if (eRowColorOptions != null) {
1299                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_MANUAL)) != null) {
1300                    _rowColorManual = a.getValue().equals(Xml.TRUE);
1301                }
1302                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_BUILD_FAILED)) != null) {
1303                    _rowColorBuildFailed = a.getValue().toLowerCase();
1304                }
1305                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_BUILT)) != null) {
1306                    _rowColorBuilt = a.getValue().toLowerCase();
1307                }
1308                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_TRAIN_EN_ROUTE)) != null) {
1309                    _rowColorTrainEnRoute = a.getValue().toLowerCase();
1310                }
1311                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_TERMINATED)) != null) {
1312                    _rowColorTerminated = a.getValue().toLowerCase();
1313                }
1314                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_RESET)) != null) {
1315                    _rowColorReset = a.getValue().toLowerCase();
1316                }
1317            }
1318
1319            // moved to train schedule manager
1320            e = options.getChild(jmri.jmrit.operations.trains.schedules.Xml.TRAIN_SCHEDULE_OPTIONS);
1321            if (e != null) {
1322                if ((a = e.getAttribute(jmri.jmrit.operations.trains.schedules.Xml.ACTIVE_ID)) != null) {
1323                    InstanceManager.getDefault(TrainScheduleManager.class).setTrainScheduleActiveId(a.getValue());
1324                }
1325            }
1326            // check for scripts
1327            if (options.getChild(Xml.SCRIPTS) != null) {
1328                List<Element> lm = options.getChild(Xml.SCRIPTS).getChildren(Xml.START_UP);
1329                for (Element es : lm) {
1330                    if ((a = es.getAttribute(Xml.NAME)) != null) {
1331                        addStartUpScript(a.getValue());
1332                    }
1333                }
1334                List<Element> lt = options.getChild(Xml.SCRIPTS).getChildren(Xml.SHUT_DOWN);
1335                for (Element es : lt) {
1336                    if ((a = es.getAttribute(Xml.NAME)) != null) {
1337                        addShutDownScript(a.getValue());
1338                    }
1339                }
1340            }
1341        }
1342        if (root.getChild(Xml.TRAINS) != null) {
1343            List<Element> eTrains = root.getChild(Xml.TRAINS).getChildren(Xml.TRAIN);
1344            log.debug("readFile sees {} trains", eTrains.size());
1345            for (Element eTrain : eTrains) {
1346                register(new Train(eTrain));
1347            }
1348        }
1349    }
1350
1351    /**
1352     * Create an XML element to represent this Entry. This member has to remain
1353     * synchronized with the detailed DTD in operations-trains.dtd.
1354     *
1355     * @param root common Element for operations-trains.dtd.
1356     */
1357    public void store(Element root) {
1358        Element options = new Element(Xml.OPTIONS);
1359        Element e = new Element(Xml.TRAIN_OPTIONS);
1360        e.setAttribute(Xml.BUILD_MESSAGES, isBuildMessagesEnabled() ? Xml.TRUE : Xml.FALSE);
1361        e.setAttribute(Xml.BUILD_REPORT, isBuildReportEnabled() ? Xml.TRUE : Xml.FALSE);
1362        e.setAttribute(Xml.PRINT_PREVIEW, isPrintPreviewEnabled() ? Xml.TRUE : Xml.FALSE);
1363        e.setAttribute(Xml.OPEN_FILE, isOpenFileEnabled() ? Xml.TRUE : Xml.FALSE);
1364        e.setAttribute(Xml.RUN_FILE, isRunFileEnabled() ? Xml.TRUE : Xml.FALSE);
1365        e.setAttribute(Xml.TRAIN_ACTION, getTrainsFrameTrainAction());
1366        options.addContent(e);
1367
1368        // Conductor options
1369        e = new Element(Xml.CONDUCTOR_OPTIONS);
1370        e.setAttribute(Xml.SHOW_HYPHEN_NAME, isShowLocationHyphenNameEnabled() ? Xml.TRUE : Xml.FALSE);
1371        options.addContent(e);
1372
1373        // Trains table row color options
1374        e = new Element(Xml.ROW_COLOR_OPTIONS);
1375        e.setAttribute(Xml.ROW_COLOR_MANUAL, isRowColorManual() ? Xml.TRUE : Xml.FALSE);
1376        e.setAttribute(Xml.ROW_COLOR_BUILD_FAILED, getRowColorNameForBuildFailed());
1377        e.setAttribute(Xml.ROW_COLOR_BUILT, getRowColorNameForBuilt());
1378        e.setAttribute(Xml.ROW_COLOR_TRAIN_EN_ROUTE, getRowColorNameForTrainEnRoute());
1379        e.setAttribute(Xml.ROW_COLOR_TERMINATED, getRowColorNameForTerminated());
1380        e.setAttribute(Xml.ROW_COLOR_RESET, getRowColorNameForReset());
1381        options.addContent(e);
1382
1383        if (getStartUpScripts().size() > 0 || getShutDownScripts().size() > 0) {
1384            // save list of shutdown scripts
1385            Element es = new Element(Xml.SCRIPTS);
1386            for (String scriptName : getStartUpScripts()) {
1387                Element em = new Element(Xml.START_UP);
1388                em.setAttribute(Xml.NAME, scriptName);
1389                es.addContent(em);
1390            }
1391            // save list of termination scripts
1392            for (String scriptName : getShutDownScripts()) {
1393                Element et = new Element(Xml.SHUT_DOWN);
1394                et.setAttribute(Xml.NAME, scriptName);
1395                es.addContent(et);
1396            }
1397            options.addContent(es);
1398        }
1399
1400        InstanceManager.getDefault(TrainCustomManifest.class).store(options); // save custom manifest elements
1401        InstanceManager.getDefault(TrainCustomSwitchList.class).store(options); // save custom switch list elements
1402
1403        root.addContent(options);
1404
1405        Element trains = new Element(Xml.TRAINS);
1406        root.addContent(trains);
1407        // add entries
1408        for (Train train : getTrainsByIdList()) {
1409            trains.addContent(train.store());
1410        }
1411        firePropertyChange(TRAINS_SAVED_PROPERTY, true, false);
1412    }
1413
1414    /**
1415     * Not currently used.
1416     */
1417    @Override
1418    public void propertyChange(java.beans.PropertyChangeEvent e) {
1419        log.debug("TrainManager sees property change: {} old: {} new: {}", e.getPropertyName(), e.getOldValue(),
1420                e.getNewValue());
1421        if (e.getPropertyName().equals(Train.NAME_CHANGED_PROPERTY)) {
1422            // reset max train name length
1423            _maxTrainNameLength = 0;
1424        }
1425    }
1426
1427    private void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
1428        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
1429        firePropertyChange(p, old, n);
1430    }
1431
1432    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainManager.class);
1433
1434    @Override
1435    public void initialize() {
1436        InstanceManager.getDefault(OperationsSetupXml.class); // load setup
1437        InstanceManager.getDefault(CarManagerXml.class); // load cars
1438        InstanceManager.getDefault(EngineManagerXml.class); // load engines
1439        InstanceManager.getDefault(TrainManagerXml.class); // load trains
1440    }
1441
1442}