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