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