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