001package jmri.jmrit.operations.locations.gui; 002 003import java.awt.Color; 004import java.awt.Dimension; 005import java.awt.event.ActionEvent; 006import java.text.MessageFormat; 007import java.util.ArrayList; 008import java.util.List; 009 010import javax.swing.*; 011 012import jmri.InstanceManager; 013import jmri.jmrit.operations.CommonConductorYardmasterPanel; 014import jmri.jmrit.operations.locations.*; 015import jmri.jmrit.operations.rollingstock.RollingStock; 016import jmri.jmrit.operations.rollingstock.cars.*; 017import jmri.jmrit.operations.rollingstock.engines.Engine; 018import jmri.jmrit.operations.routes.RouteLocation; 019import jmri.jmrit.operations.setup.Control; 020import jmri.jmrit.operations.setup.Setup; 021import jmri.jmrit.operations.trains.Train; 022import jmri.jmrit.operations.trains.TrainSwitchListText; 023import jmri.jmrit.operations.trains.trainbuilder.TrainCommon; 024 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028/** 029 * Yardmaster frame by track. Shows work at one location listed by track. 030 * 031 * @author Dan Boudreau Copyright (C) 2015, 2026 032 */ 033public class YardmasterByTrackPanel extends CommonConductorYardmasterPanel { 034 035 protected Track _track = null; 036 037 // text panes 038 JTextPane textTrackCommentPane = new JTextPane(); 039 040 // combo boxes 041 JComboBox<Track> trackComboBox = new JComboBox<>(); 042 043 // buttons 044 JButton nextButton = new JButton(Bundle.getMessage("Next")); 045 046 // panel 047 JPanel pTrack = new JPanel(); 048 JScrollPane pTrackPane; 049 050 public YardmasterByTrackPanel() { 051 this(null); 052 } 053 054 public YardmasterByTrackPanel(Location location) { 055 super(); 056 057 // this window doesn't use the set button 058 modifyButton.setVisible(false); 059 060 _location = location; 061 062 textTrackCommentPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TrackComment"))); 063 textTrackCommentPane.setBackground(null); 064 textTrackCommentPane.setEditable(false); 065 textTrackCommentPane.setMaximumSize(new Dimension(1000, 200)); 066 067 JPanel pTrackSelect = new JPanel(); 068 pTrackSelect.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Track"))); 069 pTrackSelect.add(trackComboBox); 070 // add next button for web server 071 pTrackSelect.add(nextButton); 072 073 // work at this location by track 074 pTrack.setLayout(new BoxLayout(pTrack, BoxLayout.Y_AXIS)); 075 pTrackPane = new JScrollPane(pTrack); 076 077 pLocationName.setMaximumSize(new Dimension(2000, 200)); 078 pTrackSelect.setMaximumSize(new Dimension(2000, 200)); 079 pButtons.setMaximumSize(new Dimension(2000, 200)); 080 081 add(pLocationName); 082 add(textLocationCommentPane); 083 add(textSwitchListCommentPane); 084 add(pTrackSelect); 085 add(textTrackCommentPane); 086 add(pTrackComments); 087 add(pTrackPane); 088 add(pButtons); 089 090 if (_location != null) { 091 textLocationName.setText(_location.getName()); 092 loadLocationComment(_location); 093 loadLocationSwitchListComment(_location); 094 updateTrackComboBox(); 095 _location.addPropertyChangeListener(this); 096 } 097 098 update(); 099 100 addComboBoxAction(trackComboBox); 101 addButtonAction(nextButton); 102 } 103 104 // Select, Clear, and Next Buttons 105 @Override 106 public void buttonActionPerformed(ActionEvent ae) { 107 if (ae.getSource() == nextButton) { 108 nextButtonAction(); 109 } 110 super.buttonActionPerformed(ae); 111 } 112 113 private void nextButtonAction() { 114 log.debug("next button activated"); 115 if (trackComboBox.getItemCount() > 1) { 116 int index = trackComboBox.getSelectedIndex(); 117 // index = -1 if first item (null) in trainComboBox 118 if (index == -1) { 119 index = 1; 120 } else { 121 index++; 122 } 123 if (index >= trackComboBox.getItemCount()) { 124 index = 0; 125 } 126 trackComboBox.setSelectedIndex(index); 127 } 128 } 129 130 @Override 131 protected void comboBoxActionPerformed(ActionEvent ae) { 132 // made the combo box not visible during updates, so ignore if not visible 133 if (ae.getSource() == trackComboBox && trackComboBox.isVisible()) { 134 _track = null; 135 if (trackComboBox.getSelectedItem() != null) { 136 _track = (Track) trackComboBox.getSelectedItem(); 137 } 138 update(); 139 } 140 } 141 142 @Override 143 protected void update() { 144 // use invokeLater to prevent deadlock 145 SwingUtilities.invokeLater(() -> { 146 runUpdate(); 147 }); 148 } 149 150 private void runUpdate() { 151 log.debug("run update"); 152 removePropertyChangeListerners(); 153 trainCommon.clearUtilityCarTypes(); // reset the utility car counts 154 checkBoxes.clear(); 155 pTrack.removeAll(); 156 if (_track != null) { 157 pTrackPane.setBorder(BorderFactory.createTitledBorder(_track.getSplitName())); 158 textTrackCommentPane.setText(TrainCommon.getOnlyText(_track.getComment())); 159 textTrackCommentPane.setForeground(TrainCommon.getTextColor(_track.getComment())); 160 textTrackCommentPane.setVisible(!_track.getComment().equals(Track.NONE)); 161 for (Train train : trainManager.getTrainsArrivingThisLocationList(_track.getLocation())) { 162 JPanel pTrain = new JPanel(); 163 pTrain.setLayout(new BoxLayout(pTrain, BoxLayout.Y_AXIS)); 164 pTrain.setBorder(BorderFactory 165 .createTitledBorder(MessageFormat.format(TrainSwitchListText.getStringScheduledWork(), 166 new Object[]{train.getName(), train.getDescription()}))); 167 // Track work comments 168 boolean pickupCar = false; 169 boolean setoutCar = false; 170 JTextPane textTrackCommentWorkPane = getTrackWorkCommentPane(); 171 pTrain.add(textTrackCommentWorkPane); 172 173 boolean localCar = false; 174 175 // Engine 176 boolean pickupEngine = false; 177 boolean setoutEngine = false; 178 179 // pick ups 180 JPanel pPickups = new JPanel(); 181 pPickups.setLayout(new BoxLayout(pPickups, BoxLayout.Y_AXIS)); 182 pPickups.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Pickup"))); 183 pPickups.setMaximumSize(new Dimension(2000, 2000)); 184 185 // set outs 186 JPanel pSetouts = new JPanel(); 187 pSetouts.setLayout(new BoxLayout(pSetouts, BoxLayout.Y_AXIS)); 188 pSetouts.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("SetOut"))); 189 pSetouts.setMaximumSize(new Dimension(2000, 2000)); 190 191 // local moves 192 JPanel pLocal = new JPanel(); 193 pLocal.setLayout(new BoxLayout(pLocal, BoxLayout.Y_AXIS)); 194 pLocal.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("LocalMoves"))); 195 pLocal.setMaximumSize(new Dimension(2000, 2000)); 196 197 // List locos first 198 List<Engine> engList = engManager.getByTrainBlockingList(train); 199 if (Setup.isPrintHeadersEnabled()) { 200 for (Engine engine : engList) { 201 if (engine.getSplitTrackName().equals(_track.getSplitName())) { 202 JLabel header = new JLabel( 203 Tab + trainCommon.getPickupEngineHeader(!TrainCommon.IS_TWO_COLUMN_TRACK)); 204 setLabelFont(header); 205 pPickups.add(header); 206 break; 207 } 208 } 209 } 210 for (Engine engine : engList) { 211 if (engine.getSplitTrackName().equals(_track.getSplitName())) { 212 engine.addPropertyChangeListener(this); 213 rollingStock.add(engine); 214 JCheckBox checkBox = 215 new JCheckBox(trainCommon.pickupEngine(engine, !TrainCommon.IS_TWO_COLUMN_TRACK)); 216 setCheckBoxFont(checkBox, Setup.getPickupEngineColor()); 217 pPickups.add(checkBox); 218 pickupEngine = true; 219 checkBoxes.put(engine.getId() + "p", checkBox); 220 pTrack.add(pTrain); 221 } 222 } 223 // now do locomotive set outs 224 if (Setup.isPrintHeadersEnabled()) { 225 for (Engine engine : engList) { 226 if (engine.getSplitDestinationTrackName().equals(_track.getSplitName())) { 227 JLabel header = 228 new JLabel(Tab + trainCommon.getDropEngineHeader(!TrainCommon.IS_TWO_COLUMN_TRACK)); 229 setLabelFont(header); 230 pSetouts.add(header); 231 break; 232 } 233 } 234 } 235 for (Engine engine : engList) { 236 if (engine.getSplitDestinationTrackName().equals(_track.getSplitName())) { 237 engine.addPropertyChangeListener(this); 238 rollingStock.add(engine); 239 JCheckBox checkBox = 240 new JCheckBox(trainCommon.dropEngine(engine, !TrainCommon.IS_TWO_COLUMN_TRACK)); 241 setCheckBoxFont(checkBox, Setup.getDropEngineColor()); 242 pSetouts.add(checkBox); 243 setoutEngine = true; 244 checkBoxes.put(engine.getId(), checkBox); 245 pTrack.add(pTrain); 246 } 247 } 248 // now cars 249 List<Car> carList = carManager.getByTrainDestinationList(train); 250 if (Setup.isPrintHeadersEnabled()) { 251 for (Car car : carList) { 252 if (car.getSplitTrackName().equals(_track.getSplitName()) && 253 car.getRouteDestination() != car.getRouteLocation()) { 254 JLabel header = new JLabel(Tab + 255 trainCommon.getPickupCarHeader(!IS_MANIFEST, !TrainCommon.IS_TWO_COLUMN_TRACK)); 256 setLabelFont(header); 257 pPickups.add(header); 258 break; 259 } 260 } 261 } 262 // sort car pick ups by their destination 263 List<RouteLocation> routeList = train.getRoute().getLocationsBySequenceList(); 264 for (RouteLocation rl : routeList) { 265 for (Car car : carList) { 266 if (car.getSplitTrackName().equals(_track.getSplitName()) && 267 car.getRouteDestination() != car.getRouteLocation() && 268 car.getRouteDestination() == rl) { 269 car.addPropertyChangeListener(this); 270 rollingStock.add(car); 271 String text; 272 if (car.isUtility()) { 273 text = trainCommon.pickupUtilityCars(carList, car, !IS_MANIFEST, 274 !TrainCommon.IS_TWO_COLUMN_TRACK); 275 if (text == null) { 276 continue; // this car type has already been processed 277 } 278 } else { 279 text = trainCommon.pickupCar(car, !IS_MANIFEST, !TrainCommon.IS_TWO_COLUMN_TRACK); 280 } 281 pickupCar = true; 282 JCheckBox checkBox = new JCheckBox(text); 283 setCheckBoxFont(checkBox, Setup.getPickupColor()); 284 pPickups.add(checkBox); 285 checkBoxes.put(car.getId() + "p", checkBox); 286 pTrack.add(pTrain); 287 } 288 } 289 } 290 // now do car set outs 291 if (Setup.isPrintHeadersEnabled()) { 292 for (Car car : carList) { 293 if (car.getSplitDestinationTrackName().equals(_track.getSplitName()) && 294 (car.getTrack() == null || 295 car.getRouteDestination() != car.getRouteLocation())) { 296 JLabel header = new JLabel( 297 Tab + trainCommon.getDropCarHeader(!IS_MANIFEST, !TrainCommon.IS_TWO_COLUMN_TRACK)); 298 setLabelFont(header); 299 pSetouts.add(header); 300 break; 301 } 302 } 303 } 304 for (Car car : carList) { 305 if (car.getSplitDestinationTrackName().equals(_track.getSplitName()) && 306 (car.getTrack() == null || car.getRouteLocation() != car.getRouteDestination())) { 307 car.addPropertyChangeListener(this); 308 rollingStock.add(car); 309 String text; 310 if (car.isUtility()) { 311 text = trainCommon.setoutUtilityCars(carList, car, !TrainCommon.LOCAL, !IS_MANIFEST); 312 if (text == null) { 313 continue; // this car type has already been processed 314 } 315 } else { 316 text = trainCommon.dropCar(car, !IS_MANIFEST, !TrainCommon.IS_TWO_COLUMN_TRACK); 317 } 318 setoutCar = true; 319 JCheckBox checkBox = new JCheckBox(text); 320 setCheckBoxFont(checkBox, Setup.getDropColor()); 321 pSetouts.add(checkBox); 322 checkBoxes.put(car.getId(), checkBox); 323 pTrack.add(pTrain); 324 } 325 } 326 // now do local car moves 327 if (Setup.isPrintHeadersEnabled()) { 328 for (Car car : carList) { 329 if ((car.getSplitTrackName().equals(_track.getSplitName()) || 330 car.getTrack() != null && 331 car.getSplitDestinationTrackName().equals(_track.getSplitName())) && 332 car.getRouteDestination() == car.getRouteLocation()) { 333 JLabel header = new JLabel(Tab + trainCommon.getLocalMoveHeader(!IS_MANIFEST)); 334 setLabelFont(header); 335 pLocal.add(header); 336 break; 337 } 338 } 339 } 340 for (Car car : carList) { 341 if ((car.getSplitTrackName().equals(_track.getSplitName()) || 342 car.getTrack() != null && 343 car.getSplitDestinationTrackName().equals(_track.getSplitName())) && 344 car.getRouteLocation() != null && 345 car.getRouteLocation() == car.getRouteDestination()) { 346 car.addPropertyChangeListener(this); 347 rollingStock.add(car); 348 String text; 349 if (car.isUtility()) { 350 text = trainCommon.setoutUtilityCars(carList, car, TrainCommon.LOCAL, !IS_MANIFEST); 351 if (text == null) { 352 continue; // this car type has already been processed 353 } 354 } else { 355 text = trainCommon.localMoveCar(car, !IS_MANIFEST); 356 } 357 if (car.getSplitTrackName().equals(_track.getSplitName())) { 358 pickupCar = true; 359 } 360 if (car.getSplitDestinationTrackName().equals(_track.getSplitName())) { 361 setoutCar = true; 362 } 363 JCheckBox checkBox = new JCheckBox(text); 364 setCheckBoxFont(checkBox, Setup.getLocalColor()); 365 pLocal.add(checkBox); 366 localCar = true; 367 checkBoxes.put(car.getId(), checkBox); 368 pTrack.add(pTrain); 369 } 370 } 371 if (pickupCar && !setoutCar) { 372 textTrackCommentWorkPane.setText(_track.getCommentPickup()); 373 textTrackCommentWorkPane 374 .setForeground(TrainCommon.getTextColor(_track.getCommentPickupWithColor())); 375 } else if (!pickupCar && setoutCar) { 376 textTrackCommentWorkPane.setText(_track.getCommentSetout()); 377 textTrackCommentWorkPane 378 .setForeground(TrainCommon.getTextColor(_track.getCommentSetoutWithColor())); 379 } else if (pickupCar && setoutCar) { 380 textTrackCommentWorkPane.setText(_track.getCommentBoth()); 381 textTrackCommentWorkPane.setForeground(TrainCommon.getTextColor(_track.getCommentBothWithColor())); 382 } 383 textTrackCommentWorkPane.setVisible(!textTrackCommentWorkPane.getText().isEmpty()); 384 385 // only show panels that have work 386 if (pickupCar || pickupEngine) { 387 pTrain.add(pPickups); 388 } 389 if (setoutCar || setoutEngine) { 390 pTrain.add(pSetouts); 391 } 392 if (localCar) { 393 pTrain.add(pLocal); 394 } 395 396 pTrackPane.validate(); 397 pTrain.setMaximumSize(new Dimension(2000, pTrain.getHeight())); 398 pTrain.revalidate(); 399 } 400 // now do car holds 401 // we only need the cars on this track 402 List<Car> rsList = carManager.getByTrainList(); 403 List<Car> carList = new ArrayList<Car>(); 404 for (Car rs : rsList) { 405 if (!rs.getSplitTrackName().equals(_track.getSplitName()) || rs.getRouteLocation() != null) 406 continue; 407 carList.add(rs); 408 } 409 JPanel pHoldCars = new JPanel(); 410 pHoldCars.setLayout(new BoxLayout(pHoldCars, BoxLayout.Y_AXIS)); 411 pHoldCars.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("HoldCars"))); 412 for (Car car : carList) { 413 String text; 414 if (car.isUtility()) { 415 String s = trainCommon.pickupUtilityCars(carList, car, !IS_MANIFEST, 416 !TrainCommon.IS_TWO_COLUMN_TRACK); 417 if (s == null) 418 continue; 419 text = TrainSwitchListText.getStringHoldCar().split("\\{")[0] + s.trim(); 420 } else { 421 text = MessageFormat.format(TrainSwitchListText.getStringHoldCar(), 422 new Object[]{ 423 TrainCommon.padAndTruncateIfNeeded(car.getRoadName(), 424 InstanceManager.getDefault(CarRoads.class).getMaxNameLength()), 425 TrainCommon.padAndTruncateIfNeeded(TrainCommon.splitString(car.getNumber()), 426 Control.max_len_string_print_road_number), 427 TrainCommon.padAndTruncateIfNeeded(car.getTypeName().split(TrainCommon.HYPHEN)[0], 428 InstanceManager.getDefault(CarTypes.class).getMaxNameLength()), 429 TrainCommon.padAndTruncateIfNeeded(car.getLength() + Setup.getLengthUnitAbv(), 430 Control.max_len_string_length_name), 431 TrainCommon.padAndTruncateIfNeeded(car.getLoadName(), 432 InstanceManager.getDefault(CarLoads.class).getMaxNameLength()), 433 TrainCommon.padAndTruncateIfNeeded(_track.getName(), 434 InstanceManager.getDefault(LocationManager.class).getMaxTrackNameLength()), 435 TrainCommon.padAndTruncateIfNeeded(car.getColorName(), 436 InstanceManager.getDefault(CarColors.class).getMaxNameLength())}); 437 438 } 439 JCheckBox checkBox = new JCheckBox(text); 440 setCheckBoxFont(checkBox, Color.black); 441 pHoldCars.add(checkBox); 442 checkBoxes.put(car.getId(), checkBox); 443 pTrack.add(pHoldCars); 444 } 445 pTrackPane.validate(); 446 pHoldCars.setMaximumSize(new Dimension(2000, pHoldCars.getHeight())); 447 pHoldCars.revalidate(); 448 } else { 449 pTrackPane.setBorder(BorderFactory.createTitledBorder("")); 450 textTrackCommentPane.setVisible(false); 451 } 452 } 453 454 private JTextPane getTrackWorkCommentPane() { 455 JTextPane textTrackCommentWorkPane = new JTextPane(); 456 textTrackCommentWorkPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Comment"))); 457 textTrackCommentWorkPane.setBackground(null); 458 textTrackCommentWorkPane.setEditable(false); 459 return textTrackCommentWorkPane; 460 } 461 462 private void updateTrackComboBox() { 463 Object selectedItem = trackComboBox.getSelectedItem(); 464 trackComboBox.setVisible(false); // used as a flag to ignore updates 465 if (_location != null) { 466 _location.updateComboBox(trackComboBox); 467 } 468 filterTrackComboBox(trackComboBox); 469 if (selectedItem != null) { 470 trackComboBox.setSelectedItem(selectedItem); 471 } 472 trackComboBox.setVisible(true); 473 } 474 475 // remove tracks with "similar" names 476 private void filterTrackComboBox(JComboBox<Track> box) { 477 String trackName = NONE; 478 for (int i = 0; i < box.getItemCount(); i++) { 479 Track track = box.getItemAt(i); 480 if (track != null) { 481 if (track.getSplitName().equals(trackName)) { 482 box.removeItemAt(i--); 483 } 484 trackName = track.getSplitName(); 485 } 486 } 487 } 488 489 @Override 490 public void dispose() { 491 if (_location != null) 492 _location.removePropertyChangeListener(this); 493 removePropertyChangeListerners(); 494 } 495 496 @Override 497 public void propertyChange(java.beans.PropertyChangeEvent e) { 498 if (Control.SHOW_PROPERTY) { 499 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), 500 e.getNewValue()); 501 } 502 if (e.getPropertyName().equals(RollingStock.ROUTE_LOCATION_CHANGED_PROPERTY)) { 503 update(); 504 } 505 if (e.getPropertyName().equals(Location.TRACK_LISTLENGTH_CHANGED_PROPERTY)) { 506 updateTrackComboBox(); 507 } 508 } 509 510 private static final Logger log = LoggerFactory.getLogger(YardmasterByTrackPanel.class); 511}