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