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