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 Locomotive 315 * roads. 316 * 317 * @return true if there's a restriction 318 */ 319 public boolean isLocoRoadRestricted() { 320 for (Train train : getList()) { 321 if (!train.getLocoRoadOption().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 car 330 * owners. 331 * 332 * @return true if there's a restriction 333 */ 334 public boolean isOwnerRestricted() { 335 for (Train train : getList()) { 336 if (!train.getOwnerOption().equals(Train.ALL_OWNERS)) { 337 return true; 338 } 339 } 340 return false; 341 } 342 343 public void dispose() { 344 _trainHashTable.clear(); 345 _id = 0; 346 } 347 348 // stores known Train instances by id 349 private final Hashtable<String, Train> _trainHashTable = new Hashtable<>(); 350 351 /** 352 * @param name The train's name. 353 * @return requested Train object or null if none exists 354 */ 355 public Train getTrainByName(String name) { 356 if (!InstanceManager.getDefault(TrainManagerXml.class).isTrainFileLoaded()) { 357 log.error("TrainManager getTrainByName called before trains completely loaded!"); 358 } 359 Train train; 360 Enumeration<Train> en = _trainHashTable.elements(); 361 while (en.hasMoreElements()) { 362 train = en.nextElement(); 363 // windows file names are case independent 364 if (train.getName().toLowerCase().equals(name.toLowerCase())) { 365 return train; 366 } 367 } 368 log.debug("Train ({}) doesn't exist", name); 369 return null; 370 } 371 372 public Train getTrainById(String id) { 373 if (!InstanceManager.getDefault(TrainManagerXml.class).isTrainFileLoaded()) { 374 log.error("TrainManager getTrainById called before trains completely loaded!"); 375 } 376 return _trainHashTable.get(id); 377 } 378 379 /** 380 * Finds an existing train or creates a new train if needed. Requires train's 381 * name and creates a unique id for a new train 382 * 383 * @param name The train's name. 384 * 385 * 386 * @return new train or existing train 387 */ 388 public Train newTrain(String name) { 389 Train train = getTrainByName(name); 390 if (train == null) { 391 _id++; 392 train = new Train(Integer.toString(_id), name); 393 Integer oldSize = Integer.valueOf(getNumEntries()); 394 _trainHashTable.put(train.getId(), train); 395 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, 396 Integer.valueOf(getNumEntries())); 397 } 398 return train; 399 } 400 401 /** 402 * Remember a NamedBean Object created outside the manager. 403 * 404 * @param train The Train to be added. 405 */ 406 public void register(Train train) { 407 Integer oldSize = Integer.valueOf(getNumEntries()); 408 _trainHashTable.put(train.getId(), train); 409 // find last id created 410 int id = Integer.parseInt(train.getId()); 411 if (id > _id) { 412 _id = id; 413 } 414 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(getNumEntries())); 415 } 416 417 /** 418 * Forget a NamedBean Object created outside the manager. 419 * 420 * @param train The Train to delete. 421 */ 422 public void deregister(Train train) { 423 if (train == null) { 424 return; 425 } 426 train.dispose(); 427 Integer oldSize = Integer.valueOf(getNumEntries()); 428 _trainHashTable.remove(train.getId()); 429 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(getNumEntries())); 430 } 431 432 public void replaceLoad(String type, String oldLoadName, String newLoadName) { 433 for (Train train : getTrainsByIdList()) { 434 for (String loadName : train.getLoadNames()) { 435 if (loadName.equals(oldLoadName)) { 436 train.deleteLoadName(oldLoadName); 437 if (newLoadName != null) { 438 train.addLoadName(newLoadName); 439 } 440 } 441 // adjust combination car type and load name 442 String[] splitLoad = loadName.split(CarLoad.SPLIT_CHAR); 443 if (splitLoad.length > 1) { 444 if (splitLoad[0].equals(type) && splitLoad[1].equals(oldLoadName)) { 445 train.deleteLoadName(loadName); 446 if (newLoadName != null) { 447 train.addLoadName(type + CarLoad.SPLIT_CHAR + newLoadName); 448 } 449 } 450 } 451 } 452 } 453 } 454 455 /** 456 * 457 * @return true if there's a built train 458 */ 459 public boolean isAnyTrainBuilt() { 460 for (Train train : getTrainsByIdList()) { 461 if (train.isBuilt()) { 462 return true; 463 } 464 } 465 return false; 466 } 467 468 /** 469 * 470 * @return true if there's a train being built 471 */ 472 public boolean isAnyTrainBuilding() { 473 for (Train train : getTrainsByIdList()) { 474 if (train.getStatusCode() == Train.CODE_BUILDING) { 475 log.debug("Train {} is currently building", train.getName()); 476 return true; 477 } 478 } 479 return false; 480 } 481 482 /** 483 * @param car The car looking for a train. 484 * @param buildReport The optional build report for logging. 485 * @return Train that can service car from its current location to the its 486 * destination. 487 */ 488 public Train getTrainForCar(Car car, PrintWriter buildReport) { 489 return getTrainForCar(car, new ArrayList<>(), buildReport); 490 } 491 492 /** 493 * @param car The car looking for a train. 494 * @param excludeTrains The trains not to try. 495 * @param buildReport The optional build report for logging. 496 * @return Train that can service car from its current location to the its 497 * destination. 498 */ 499 public Train getTrainForCar(Car car, List<Train> excludeTrains, PrintWriter buildReport) { 500 addLine(buildReport, TrainCommon.BLANK_LINE); 501 addLine(buildReport, Bundle.getMessage("trainFindForCar", car.toString(), car.getLocationName(), 502 car.getTrackName(), car.getDestinationName(), car.getDestinationTrackName())); 503 504 main: for (Train train : getTrainsByNameList()) { 505 if (excludeTrains.contains(train)) { 506 continue; 507 } 508 if (Setup.isOnlyActiveTrainsEnabled() && !train.isBuildEnabled()) { 509 continue; 510 } 511 for (Train t : excludeTrains) { 512 if (t != null && train.getRoute() == t.getRoute()) { 513 addLine(buildReport, Bundle.getMessage("trainHasSameRoute", train, t)); 514 continue main; 515 } 516 } 517 // does this train service this car? 518 if (train.isServiceable(buildReport, car)) { 519 log.debug("Found train ({}) for car ({}) location ({}, {}) destination ({}, {})", train.getName(), 520 car.toString(), car.getLocationName(), car.getTrackName(), car.getDestinationName(), 521 car.getDestinationTrackName()); // NOI18N 522 return train; 523 } 524 } 525 return null; 526 } 527 528 protected static final String SEVEN = Setup.BUILD_REPORT_VERY_DETAILED; 529 530 private void addLine(PrintWriter buildReport, String string) { 531 if (Setup.getRouterBuildReportLevel().equals(SEVEN)) { 532 TrainCommon.addLine(buildReport, SEVEN, string); 533 } 534 } 535 536 /** 537 * Sort by train name 538 * 539 * @return list of trains ordered by name 540 */ 541 public List<Train> getTrainsByNameList() { 542 return getTrainsByList(getList(), GET_TRAIN_NAME); 543 } 544 545 /** 546 * Sort by train departure time 547 * 548 * @return list of trains ordered by departure time 549 */ 550 public List<Train> getTrainsByTimeList() { 551 return getTrainsByIntList(getTrainsByNameList(), GET_TRAIN_TIME); 552 } 553 554 /** 555 * Sort by train departure location name 556 * 557 * @return list of trains ordered by departure name 558 */ 559 public List<Train> getTrainsByDepartureList() { 560 return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_DEPARTES_NAME); 561 } 562 563 /** 564 * Sort by train termination location name 565 * 566 * @return list of trains ordered by termination name 567 */ 568 public List<Train> getTrainsByTerminatesList() { 569 return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_TERMINATES_NAME); 570 } 571 572 /** 573 * Sort by train route name 574 * 575 * @return list of trains ordered by route name 576 */ 577 public List<Train> getTrainsByRouteList() { 578 return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_ROUTE_NAME); 579 } 580 581 /** 582 * Sort by train status 583 * 584 * @return list of trains ordered by status 585 */ 586 public List<Train> getTrainsByStatusList() { 587 return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_STATUS); 588 } 589 590 /** 591 * Sort by train description 592 * 593 * @return list of trains ordered by train description 594 */ 595 public List<Train> getTrainsByDescriptionList() { 596 return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_DESCRIPTION); 597 } 598 599 /** 600 * Sort by train id 601 * 602 * @return list of trains ordered by id 603 */ 604 public List<Train> getTrainsByIdList() { 605 return getTrainsByIntList(getList(), GET_TRAIN_ID); 606 } 607 608 private List<Train> getTrainsByList(List<Train> sortList, int attribute) { 609 List<Train> out = new ArrayList<>(); 610 for (Train train : sortList) { 611 String trainAttribute = (String) getTrainAttribute(train, attribute); 612 for (int j = 0; j < out.size(); j++) { 613 if (trainAttribute.compareToIgnoreCase((String) getTrainAttribute(out.get(j), attribute)) < 0) { 614 out.add(j, train); 615 break; 616 } 617 } 618 if (!out.contains(train)) { 619 out.add(train); 620 } 621 } 622 return out; 623 } 624 625 private List<Train> getTrainsByIntList(List<Train> sortList, int attribute) { 626 List<Train> out = new ArrayList<>(); 627 for (Train train : sortList) { 628 int trainAttribute = (Integer) getTrainAttribute(train, attribute); 629 for (int j = 0; j < out.size(); j++) { 630 if (trainAttribute < (Integer) getTrainAttribute(out.get(j), attribute)) { 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 // the various sort options for trains 643 private static final int GET_TRAIN_DEPARTES_NAME = 0; 644 private static final int GET_TRAIN_NAME = 1; 645 private static final int GET_TRAIN_ROUTE_NAME = 2; 646 private static final int GET_TRAIN_TERMINATES_NAME = 3; 647 private static final int GET_TRAIN_TIME = 4; 648 private static final int GET_TRAIN_STATUS = 5; 649 private static final int GET_TRAIN_ID = 6; 650 private static final int GET_TRAIN_DESCRIPTION = 7; 651 652 private Object getTrainAttribute(Train train, int attribute) { 653 switch (attribute) { 654 case GET_TRAIN_DEPARTES_NAME: 655 return train.getTrainDepartsName(); 656 case GET_TRAIN_NAME: 657 return train.getName(); 658 case GET_TRAIN_ROUTE_NAME: 659 return train.getTrainRouteName(); 660 case GET_TRAIN_TERMINATES_NAME: 661 return train.getTrainTerminatesName(); 662 case GET_TRAIN_TIME: 663 return train.getDepartTimeMinutes(); 664 case GET_TRAIN_STATUS: 665 return train.getStatus(); 666 case GET_TRAIN_ID: 667 return Integer.parseInt(train.getId()); 668 case GET_TRAIN_DESCRIPTION: 669 return train.getDescription(); 670 default: 671 return "unknown"; // NOI18N 672 } 673 } 674 675 private List<Train> getList() { 676 if (!InstanceManager.getDefault(TrainManagerXml.class).isTrainFileLoaded()) { 677 log.error("TrainManager getList called before trains completely loaded!"); 678 } 679 List<Train> out = new ArrayList<>(); 680 Enumeration<Train> en = _trainHashTable.elements(); 681 while (en.hasMoreElements()) { 682 out.add(en.nextElement()); 683 } 684 return out; 685 } 686 687 public JComboBox<Train> getTrainComboBox() { 688 JComboBox<Train> box = new JComboBox<>(); 689 updateTrainComboBox(box); 690 OperationsPanel.padComboBox(box); 691 return box; 692 } 693 694 public void updateTrainComboBox(JComboBox<Train> box) { 695 box.removeAllItems(); 696 box.addItem(null); 697 for (Train train : getTrainsByNameList()) { 698 box.addItem(train); 699 } 700 } 701 702 /** 703 * Update combo box with trains that will service this car 704 * 705 * @param box the combo box to update 706 * @param car the car to be serviced 707 */ 708 public void updateTrainComboBox(JComboBox<Train> box, Car car) { 709 box.removeAllItems(); 710 box.addItem(null); 711 for (Train train : getTrainsByNameList()) { 712 if (train.isServiceable(car)) { 713 box.addItem(train); 714 } 715 } 716 } 717 718 public boolean isRowColorManual() { 719 return _rowColorManual; 720 } 721 722 public void setRowColorsManual(boolean manual) { 723 boolean old = _rowColorManual; 724 _rowColorManual = manual; 725 setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, manual); 726 } 727 728 public String getRowColorNameForBuilt() { 729 return _rowColorBuilt; 730 } 731 732 public void setRowColorNameForBuilt(String colorName) { 733 String old = _rowColorBuilt; 734 _rowColorBuilt = colorName; 735 setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName); 736 } 737 738 public String getRowColorNameForBuildFailed() { 739 return _rowColorBuildFailed; 740 } 741 742 public void setRowColorNameForBuildFailed(String colorName) { 743 String old = _rowColorBuildFailed; 744 _rowColorBuildFailed = colorName; 745 setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName); 746 } 747 748 public String getRowColorNameForTrainEnRoute() { 749 return _rowColorTrainEnRoute; 750 } 751 752 public void setRowColorNameForTrainEnRoute(String colorName) { 753 String old = _rowColorTrainEnRoute; 754 _rowColorTrainEnRoute = colorName; 755 setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName); 756 } 757 758 public String getRowColorNameForTerminated() { 759 return _rowColorTerminated; 760 } 761 762 public void setRowColorNameForTerminated(String colorName) { 763 String old = _rowColorTerminated; 764 _rowColorTerminated = colorName; 765 setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName); 766 } 767 768 public String getRowColorNameForReset() { 769 return _rowColorReset; 770 } 771 772 public void setRowColorNameForReset(String colorName) { 773 String old = _rowColorReset; 774 _rowColorReset = colorName; 775 setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName); 776 } 777 778 /** 779 * JColorChooser is not a replacement for getRowColorComboBox as it doesn't 780 * support no color as a selection. 781 * 782 * @return the available colors used highlighting table rows including no color. 783 */ 784 public JComboBox<String> getRowColorComboBox() { 785 JComboBox<String> box = new JComboBox<>(); 786 box.addItem(NONE); 787 box.addItem(ColorUtil.ColorBlack); 788 box.addItem(ColorUtil.ColorRed); 789 box.addItem(ColorUtil.ColorPink); 790 box.addItem(ColorUtil.ColorOrange); 791 box.addItem(ColorUtil.ColorYellow); 792 box.addItem(ColorUtil.ColorGreen); 793 box.addItem(ColorUtil.ColorMagenta); 794 box.addItem(ColorUtil.ColorCyan); 795 box.addItem(ColorUtil.ColorBlue); 796 box.addItem(ColorUtil.ColorGray); 797 return box; 798 } 799 800 /** 801 * Makes a copy of an existing train. 802 * 803 * @param train the train to copy 804 * @param trainName the name of the new train 805 * @return a copy of train 806 */ 807 public Train copyTrain(Train train, String trainName) { 808 Train newTrain = newTrain(trainName); 809 // route, departure time and types 810 newTrain.setRoute(train.getRoute()); 811 newTrain.setTrainSkipsLocations(train.getTrainSkipsLocations()); 812 newTrain.setDepartureTime(train.getDepartureTimeHour(), train.getDepartureTimeMinute()); 813 newTrain._typeList.clear(); // remove all types loaded by create 814 newTrain.setTypeNames(train.getTypeNames()); 815 // set road, load, and owner options 816 newTrain.setCarRoadOption(train.getCarRoadOption()); 817 newTrain.setCarRoadNames(train.getCarRoadNames()); 818 newTrain.setLocoRoadOption(train.getLocoRoadOption()); 819 newTrain.setLocoRoadNames(train.getLocoRoadNames()); 820 newTrain.setLoadOption(train.getLoadOption()); 821 newTrain.setLoadNames(train.getLoadNames()); 822 newTrain.setOwnerOption(train.getOwnerOption()); 823 newTrain.setOwnerNames(train.getOwnerNames()); 824 // build dates 825 newTrain.setBuiltStartYear(train.getBuiltStartYear()); 826 newTrain.setBuiltEndYear(train.getBuiltEndYear()); 827 // locos start of route 828 newTrain.setNumberEngines(train.getNumberEngines()); 829 newTrain.setEngineModel(train.getEngineModel()); 830 newTrain.setEngineRoad(train.getEngineRoad()); 831 newTrain.setRequirements(train.getRequirements()); 832 newTrain.setCabooseRoad(train.getCabooseRoad()); 833 // second leg 834 newTrain.setSecondLegNumberEngines(train.getSecondLegNumberEngines()); 835 newTrain.setSecondLegEngineModel(train.getSecondLegEngineModel()); 836 newTrain.setSecondLegEngineRoad(train.getSecondLegEngineRoad()); 837 newTrain.setSecondLegOptions(train.getSecondLegOptions()); 838 newTrain.setSecondLegCabooseRoad(train.getSecondLegCabooseRoad()); 839 newTrain.setSecondLegStartRouteLocation(train.getSecondLegStartRouteLocation()); 840 newTrain.setSecondLegEndRouteLocation(train.getSecondLegEndRouteLocation()); 841 // third leg 842 newTrain.setThirdLegNumberEngines(train.getThirdLegNumberEngines()); 843 newTrain.setThirdLegEngineModel(train.getThirdLegEngineModel()); 844 newTrain.setThirdLegEngineRoad(train.getThirdLegEngineRoad()); 845 newTrain.setThirdLegOptions(train.getThirdLegOptions()); 846 newTrain.setThirdLegCabooseRoad(train.getThirdLegCabooseRoad()); 847 newTrain.setThirdLegStartRouteLocation(train.getThirdLegStartRouteLocation()); 848 newTrain.setThirdLegEndRouteLocation(train.getThirdLegEndRouteLocation()); 849 // scripts 850 for (String scriptName : train.getBuildScripts()) { 851 newTrain.addBuildScript(scriptName); 852 } 853 for (String scriptName : train.getMoveScripts()) { 854 newTrain.addMoveScript(scriptName); 855 } 856 for (String scriptName : train.getTerminationScripts()) { 857 newTrain.addTerminationScript(scriptName); 858 } 859 // manifest options 860 newTrain.setRailroadName(train.getRailroadName()); 861 newTrain.setManifestLogoPathName(train.getManifestLogoPathName()); 862 newTrain.setShowArrivalAndDepartureTimes(train.isShowArrivalAndDepartureTimesEnabled()); 863 // build options 864 newTrain.setAllowLocalMovesEnabled(train.isAllowLocalMovesEnabled()); 865 newTrain.setAllowReturnToStagingEnabled(train.isAllowReturnToStagingEnabled()); 866 newTrain.setAllowThroughCarsEnabled(train.isAllowThroughCarsEnabled()); 867 newTrain.setBuildConsistEnabled(train.isBuildConsistEnabled()); 868 newTrain.setSendCarsWithCustomLoadsToStagingEnabled(train.isSendCarsWithCustomLoadsToStagingEnabled()); 869 newTrain.setBuildTrainNormalEnabled(train.isBuildTrainNormalEnabled()); 870 newTrain.setSendCarsToTerminalEnabled(train.isSendCarsToTerminalEnabled()); 871 newTrain.setServiceAllCarsWithFinalDestinationsEnabled(train.isServiceAllCarsWithFinalDestinationsEnabled()); 872 // comment 873 newTrain.setComment(train.getCommentWithColor()); 874 // description 875 newTrain.setDescription(train.getRawDescription()); 876 return newTrain; 877 } 878 879 /** 880 * Provides a list of trains ordered by arrival time to a location 881 * 882 * @param location The location 883 * @return A list of trains ordered by arrival time. 884 */ 885 public List<Train> getTrainsArrivingThisLocationList(Location location) { 886 // get a list of trains 887 List<Train> out = new ArrayList<>(); 888 List<Integer> arrivalTimes = new ArrayList<>(); 889 for (Train train : getTrainsByTimeList()) { 890 if (!train.isBuilt()) { 891 continue; // train wasn't built so skip 892 } 893 Route route = train.getRoute(); 894 if (route == null) { 895 continue; // no route for this train 896 } 897 for (RouteLocation rl : route.getLocationsBySequenceList()) { 898 if (rl.getSplitName().equals(location.getSplitName())) { 899 int expectedArrivalTime = train.getExpectedTravelTimeInMinutes(rl); 900 // is already serviced then "-1" 901 if (expectedArrivalTime == -1) { 902 out.add(0, train); // place all trains that have already been serviced at the start 903 arrivalTimes.add(0, expectedArrivalTime); 904 } // if the train is in route, then expected arrival time is in minutes 905 else if (train.isTrainEnRoute()) { 906 for (int j = 0; j < out.size(); j++) { 907 Train t = out.get(j); 908 int time = arrivalTimes.get(j); 909 if (t.isTrainEnRoute() && expectedArrivalTime < time) { 910 out.add(j, train); 911 arrivalTimes.add(j, expectedArrivalTime); 912 break; 913 } 914 if (!t.isTrainEnRoute()) { 915 out.add(j, train); 916 arrivalTimes.add(j, expectedArrivalTime); 917 break; 918 } 919 } 920 // Train has not departed 921 } else { 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 } 931 } 932 if (!out.contains(train)) { 933 out.add(train); 934 arrivalTimes.add(expectedArrivalTime); 935 } 936 break; // done 937 } 938 } 939 } 940 return out; 941 } 942 943 /** 944 * Loads train icons if needed 945 */ 946 public void loadTrainIcons() { 947 for (Train train : getTrainsByIdList()) { 948 train.loadTrainIcon(); 949 } 950 } 951 952 /** 953 * Sets the switch list status for all built trains. Used for switch lists in 954 * consolidated mode. 955 * 956 * @param status Train.PRINTED, Train.UNKNOWN 957 */ 958 public void setTrainsSwitchListStatus(String status) { 959 for (Train train : getTrainsByTimeList()) { 960 if (!train.isBuilt()) { 961 continue; // train isn't built so skip 962 } 963 train.setSwitchListStatus(status); 964 } 965 } 966 967 /** 968 * Sets all built trains manifests to modified. This causes the train's manifest 969 * to be recreated. 970 */ 971 public void setTrainsModified() { 972 for (Train train : getTrainsByTimeList()) { 973 if (!train.isBuilt() || train.isTrainEnRoute()) { 974 continue; // train wasn't built or in route, so skip 975 } 976 train.setModified(true); 977 } 978 } 979 980 public void buildSelectedTrains(List<Train> trains) { 981 // use a thread to allow table updates during build 982 Thread build = jmri.util.ThreadingUtil.newThread(new Runnable() { 983 @Override 984 public void run() { 985 for (Train train : trains) { 986 if (train.buildIfSelected()) { 987 continue; 988 } 989 if (isBuildMessagesEnabled() && train.isBuildEnabled() && !train.isBuilt()) { 990 if (JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("ContinueBuilding"), 991 Bundle.getMessage("buildFailedMsg", 992 train.getName()), 993 JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.NO_OPTION) { 994 break; 995 } 996 } 997 } 998 setDirtyAndFirePropertyChange(TRAINS_BUILT_CHANGED_PROPERTY, false, true); 999 } 1000 }); 1001 build.setName("Build Trains"); // NOI18N 1002 build.start(); 1003 } 1004 1005 public boolean printSelectedTrains(List<Train> trains) { 1006 boolean status = true; 1007 for (Train train : trains) { 1008 if (train.isBuildEnabled()) { 1009 if (train.printManifestIfBuilt()) { 1010 continue; 1011 } 1012 status = false; // failed to print all selected trains 1013 if (isBuildMessagesEnabled()) { 1014 int response = JmriJOptionPane.showConfirmDialog(null, 1015 Bundle.getMessage("NeedToBuildBeforePrinting", 1016 train.getName(), 1017 (isPrintPreviewEnabled() ? Bundle.getMessage("preview") 1018 : Bundle.getMessage("print"))), 1019 Bundle.getMessage("CanNotPrintManifest", 1020 isPrintPreviewEnabled() ? Bundle.getMessage("preview") 1021 : Bundle.getMessage("print")), 1022 JmriJOptionPane.OK_CANCEL_OPTION); 1023 if (response != JmriJOptionPane.OK_OPTION ) { 1024 break; 1025 } 1026 } 1027 } 1028 } 1029 return status; 1030 } 1031 1032 public boolean terminateSelectedTrains(List<Train> trains) { 1033 boolean status = true; 1034 for (Train train : trains) { 1035 if (train.isBuildEnabled() && train.isBuilt()) { 1036 if (train.isPrinted()) { 1037 train.terminate(); 1038 } else { 1039 status = false; 1040 int response = JmriJOptionPane.showConfirmDialog(null, 1041 Bundle.getMessage("WarningTrainManifestNotPrinted"), 1042 Bundle.getMessage("TerminateTrain", 1043 train.getName(), train.getDescription()), 1044 JmriJOptionPane.YES_NO_CANCEL_OPTION); 1045 if (response == JmriJOptionPane.YES_OPTION) { 1046 train.terminate(); 1047 } 1048 // else Quit? 1049 if (response == JmriJOptionPane.CLOSED_OPTION || response == JmriJOptionPane.CANCEL_OPTION) { 1050 break; 1051 } 1052 } 1053 } 1054 } 1055 return status; 1056 } 1057 1058 public void resetBuildFailedTrains() { 1059 for (Train train : getList()) { 1060 if (train.isBuildFailed()) 1061 train.reset(); 1062 } 1063 } 1064 1065 int _maxTrainNameLength = 0; 1066 1067 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 1068 justification="I18N of Info Message") 1069 public int getMaxTrainNameLength() { 1070 String trainName = ""; 1071 if (_maxTrainNameLength == 0) { 1072 for (Train train : getList()) { 1073 if (train.getName().length() > _maxTrainNameLength) { 1074 trainName = train.getName(); 1075 _maxTrainNameLength = train.getName().length(); 1076 } 1077 } 1078 log.info(Bundle.getMessage("InfoMaxName", trainName, _maxTrainNameLength)); 1079 } 1080 return _maxTrainNameLength; 1081 } 1082 1083 public void load(Element root) { 1084 if (root.getChild(Xml.OPTIONS) != null) { 1085 Element options = root.getChild(Xml.OPTIONS); 1086 InstanceManager.getDefault(TrainCustomManifest.class).load(options); 1087 InstanceManager.getDefault(TrainCustomSwitchList.class).load(options); 1088 Element e = options.getChild(Xml.TRAIN_OPTIONS); 1089 Attribute a; 1090 if (e != null) { 1091 if ((a = e.getAttribute(Xml.BUILD_MESSAGES)) != null) { 1092 _buildMessages = a.getValue().equals(Xml.TRUE); 1093 } 1094 if ((a = e.getAttribute(Xml.BUILD_REPORT)) != null) { 1095 _buildReport = a.getValue().equals(Xml.TRUE); 1096 } 1097 if ((a = e.getAttribute(Xml.PRINT_PREVIEW)) != null) { 1098 _printPreview = a.getValue().equals(Xml.TRUE); 1099 } 1100 if ((a = e.getAttribute(Xml.OPEN_FILE)) != null) { 1101 _openFile = a.getValue().equals(Xml.TRUE); 1102 } 1103 if ((a = e.getAttribute(Xml.RUN_FILE)) != null) { 1104 _runFile = a.getValue().equals(Xml.TRUE); 1105 } 1106 // verify that the Trains Window action is valid 1107 if ((a = e.getAttribute(Xml.TRAIN_ACTION)) != null && 1108 (a.getValue().equals(TrainsTableFrame.MOVE) || 1109 a.getValue().equals(TrainsTableFrame.RESET) || 1110 a.getValue().equals(TrainsTableFrame.TERMINATE) || 1111 a.getValue().equals(TrainsTableFrame.CONDUCTOR))) { 1112 _trainAction = a.getValue(); 1113 } 1114 } 1115 1116 // Conductor options 1117 Element eConductorOptions = options.getChild(Xml.CONDUCTOR_OPTIONS); 1118 if (eConductorOptions != null) { 1119 if ((a = eConductorOptions.getAttribute(Xml.SHOW_HYPHEN_NAME)) != null) { 1120 _showLocationHyphenName = a.getValue().equals(Xml.TRUE); 1121 } 1122 } 1123 1124 // Row color options 1125 Element eRowColorOptions = options.getChild(Xml.ROW_COLOR_OPTIONS); 1126 if (eRowColorOptions != null) { 1127 if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_MANUAL)) != null) { 1128 _rowColorManual = a.getValue().equals(Xml.TRUE); 1129 } 1130 if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_BUILD_FAILED)) != null) { 1131 _rowColorBuildFailed = a.getValue().toLowerCase(); 1132 } 1133 if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_BUILT)) != null) { 1134 _rowColorBuilt = a.getValue().toLowerCase(); 1135 } 1136 if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_TRAIN_EN_ROUTE)) != null) { 1137 _rowColorTrainEnRoute = a.getValue().toLowerCase(); 1138 } 1139 if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_TERMINATED)) != null) { 1140 _rowColorTerminated = a.getValue().toLowerCase(); 1141 } 1142 if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_RESET)) != null) { 1143 _rowColorReset = a.getValue().toLowerCase(); 1144 } 1145 } 1146 1147 // moved to train schedule manager 1148 e = options.getChild(jmri.jmrit.operations.trains.schedules.Xml.TRAIN_SCHEDULE_OPTIONS); 1149 if (e != null) { 1150 if ((a = e.getAttribute(jmri.jmrit.operations.trains.schedules.Xml.ACTIVE_ID)) != null) { 1151 InstanceManager.getDefault(TrainScheduleManager.class).setTrainScheduleActiveId(a.getValue()); 1152 } 1153 } 1154 // check for scripts 1155 if (options.getChild(Xml.SCRIPTS) != null) { 1156 List<Element> lm = options.getChild(Xml.SCRIPTS).getChildren(Xml.START_UP); 1157 for (Element es : lm) { 1158 if ((a = es.getAttribute(Xml.NAME)) != null) { 1159 addStartUpScript(a.getValue()); 1160 } 1161 } 1162 List<Element> lt = options.getChild(Xml.SCRIPTS).getChildren(Xml.SHUT_DOWN); 1163 for (Element es : lt) { 1164 if ((a = es.getAttribute(Xml.NAME)) != null) { 1165 addShutDownScript(a.getValue()); 1166 } 1167 } 1168 } 1169 } 1170 if (root.getChild(Xml.TRAINS) != null) { 1171 List<Element> eTrains = root.getChild(Xml.TRAINS).getChildren(Xml.TRAIN); 1172 log.debug("readFile sees {} trains", eTrains.size()); 1173 for (Element eTrain : eTrains) { 1174 register(new Train(eTrain)); 1175 } 1176 } 1177 } 1178 1179 /** 1180 * Create an XML element to represent this Entry. This member has to remain 1181 * synchronized with the detailed DTD in operations-trains.dtd. 1182 * 1183 * @param root common Element for operations-trains.dtd. 1184 * 1185 */ 1186 public void store(Element root) { 1187 Element options = new Element(Xml.OPTIONS); 1188 Element e = new Element(Xml.TRAIN_OPTIONS); 1189 e.setAttribute(Xml.BUILD_MESSAGES, isBuildMessagesEnabled() ? Xml.TRUE : Xml.FALSE); 1190 e.setAttribute(Xml.BUILD_REPORT, isBuildReportEnabled() ? Xml.TRUE : Xml.FALSE); 1191 e.setAttribute(Xml.PRINT_PREVIEW, isPrintPreviewEnabled() ? Xml.TRUE : Xml.FALSE); 1192 e.setAttribute(Xml.OPEN_FILE, isOpenFileEnabled() ? Xml.TRUE : Xml.FALSE); 1193 e.setAttribute(Xml.RUN_FILE, isRunFileEnabled() ? Xml.TRUE : Xml.FALSE); 1194 e.setAttribute(Xml.TRAIN_ACTION, getTrainsFrameTrainAction()); 1195 options.addContent(e); 1196 1197 // Conductor options 1198 e = new Element(Xml.CONDUCTOR_OPTIONS); 1199 e.setAttribute(Xml.SHOW_HYPHEN_NAME, isShowLocationHyphenNameEnabled() ? Xml.TRUE : Xml.FALSE); 1200 options.addContent(e); 1201 1202 // Trains table row color options 1203 e = new Element(Xml.ROW_COLOR_OPTIONS); 1204 e.setAttribute(Xml.ROW_COLOR_MANUAL, isRowColorManual() ? Xml.TRUE : Xml.FALSE); 1205 e.setAttribute(Xml.ROW_COLOR_BUILD_FAILED, getRowColorNameForBuildFailed()); 1206 e.setAttribute(Xml.ROW_COLOR_BUILT, getRowColorNameForBuilt()); 1207 e.setAttribute(Xml.ROW_COLOR_TRAIN_EN_ROUTE, getRowColorNameForTrainEnRoute()); 1208 e.setAttribute(Xml.ROW_COLOR_TERMINATED, getRowColorNameForTerminated()); 1209 e.setAttribute(Xml.ROW_COLOR_RESET, getRowColorNameForReset()); 1210 options.addContent(e); 1211 1212 if (getStartUpScripts().size() > 0 || getShutDownScripts().size() > 0) { 1213 // save list of shutdown scripts 1214 Element es = new Element(Xml.SCRIPTS); 1215 for (String scriptName : getStartUpScripts()) { 1216 Element em = new Element(Xml.START_UP); 1217 em.setAttribute(Xml.NAME, scriptName); 1218 es.addContent(em); 1219 } 1220 // save list of termination scripts 1221 for (String scriptName : getShutDownScripts()) { 1222 Element et = new Element(Xml.SHUT_DOWN); 1223 et.setAttribute(Xml.NAME, scriptName); 1224 es.addContent(et); 1225 } 1226 options.addContent(es); 1227 } 1228 1229 InstanceManager.getDefault(TrainCustomManifest.class).store(options); // save custom manifest elements 1230 InstanceManager.getDefault(TrainCustomSwitchList.class).store(options); // save custom switch list elements 1231 1232 root.addContent(options); 1233 1234 Element trains = new Element(Xml.TRAINS); 1235 root.addContent(trains); 1236 // add entries 1237 for (Train train : getTrainsByIdList()) { 1238 trains.addContent(train.store()); 1239 } 1240 firePropertyChange(TRAINS_SAVED_PROPERTY, true, false); 1241 } 1242 1243 /** 1244 * Not currently used. 1245 */ 1246 @Override 1247 public void propertyChange(java.beans.PropertyChangeEvent e) { 1248 log.debug("TrainManager sees property change: {} old: {} new: {}", e.getPropertyName(), e.getOldValue(), 1249 e.getNewValue()); 1250 } 1251 1252 private void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 1253 InstanceManager.getDefault(TrainManagerXml.class).setDirty(true); 1254 firePropertyChange(p, old, n); 1255 } 1256 1257 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainManager.class); 1258 1259 @Override 1260 public void initialize() { 1261 InstanceManager.getDefault(OperationsSetupXml.class); // load setup 1262 InstanceManager.getDefault(TrainManagerXml.class); // load trains 1263 } 1264 1265}