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