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