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