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