001package jmri.jmrit.operations.locations.tools; 002 003import java.awt.*; 004import java.util.List; 005 006import javax.swing.*; 007 008import jmri.InstanceManager; 009import jmri.jmrit.operations.OperationsFrame; 010import jmri.jmrit.operations.OperationsXml; 011import jmri.jmrit.operations.locations.*; 012import jmri.jmrit.operations.rollingstock.RollingStock; 013import jmri.jmrit.operations.rollingstock.cars.*; 014import jmri.jmrit.operations.router.Router; 015import jmri.jmrit.operations.setup.Control; 016import jmri.jmrit.operations.setup.Setup; 017import jmri.util.swing.JmriJOptionPane; 018 019/** 020 * Frame for user edit of track destinations 021 * 022 * @author Dan Boudreau Copyright (C) 2013, 2024 023 * 024 */ 025public class TrackDestinationEditFrame extends OperationsFrame implements java.beans.PropertyChangeListener { 026 027 Track _track = null; 028 029 LocationManager locationManager = InstanceManager.getDefault(LocationManager.class); 030 031 // panels 032 JPanel pControls = new JPanel(); 033 JPanel panelDestinations = new JPanel(); 034 JScrollPane paneDestinations = new JScrollPane(panelDestinations); 035 036 // major buttons 037 JButton saveButton = new JButton(Bundle.getMessage("ButtonSave")); 038 JButton checkDestinationsButton = new JButton(Bundle.getMessage("CheckDestinations")); 039 040 // radio buttons 041 JRadioButton destinationsAll = new JRadioButton(Bundle.getMessage("AcceptAll")); 042 JRadioButton destinationsInclude = new JRadioButton(Bundle.getMessage("AcceptOnly")); 043 JRadioButton destinationsExclude = new JRadioButton(Bundle.getMessage("Exclude")); 044 045 // checkboxes 046 JCheckBox onlyCarsWithFD = new JCheckBox(Bundle.getMessage("OnlyCarsWithFD")); 047 048 // labels 049 JLabel trackName = new JLabel(); 050 051 public static final String DISPOSE = "dispose"; // NOI18N 052 053 public TrackDestinationEditFrame() { 054 super(Bundle.getMessage("TitleEditTrackDestinations")); 055 } 056 057 public void initComponents(TrackEditFrame tef) { 058 _track = tef._track; 059 060 // the following code sets the frame's initial state 061 getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); 062 063 // Layout the panel by rows 064 // row 1 065 JPanel p1 = new JPanel(); 066 p1.setLayout(new BoxLayout(p1, BoxLayout.X_AXIS)); 067 p1.setMaximumSize(new Dimension(2000, 250)); 068 069 // row 1a 070 JPanel pTrackName = new JPanel(); 071 pTrackName.setLayout(new GridBagLayout()); 072 pTrackName.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Track"))); 073 addItem(pTrackName, trackName, 0, 0); 074 075 // row 1b 076 JPanel pLocationName = new JPanel(); 077 pLocationName.setLayout(new GridBagLayout()); 078 pLocationName.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Location"))); 079 addItem(pLocationName, new JLabel(_track.getLocation().getName()), 0, 0); 080 081 p1.add(pTrackName); 082 p1.add(pLocationName); 083 084 // row 3 085 JPanel p3 = new JPanel(); 086 p3.setLayout(new BoxLayout(p3, BoxLayout.Y_AXIS)); 087 JScrollPane pane3 = new JScrollPane(p3); 088 pane3.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("DestinationTrack"))); 089 pane3.setMaximumSize(new Dimension(2000, 400)); 090 091 JPanel pRadioButtons = new JPanel(); 092 pRadioButtons.setLayout(new FlowLayout()); 093 094 pRadioButtons.add(destinationsAll); 095 pRadioButtons.add(destinationsInclude); 096 pRadioButtons.add(destinationsExclude); 097 098 p3.add(pRadioButtons); 099 100 // row 4 only for C/I and Staging 101 JPanel pFD = new JPanel(); 102 pFD.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Options"))); 103 pFD.add(onlyCarsWithFD); 104 pFD.setMaximumSize(new Dimension(2000, 200)); 105 106 // row 5 107 panelDestinations.setLayout(new GridBagLayout()); 108 paneDestinations.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Destinations"))); 109 110 ButtonGroup bGroup = new ButtonGroup(); 111 bGroup.add(destinationsAll); 112 bGroup.add(destinationsInclude); 113 bGroup.add(destinationsExclude); 114 115 // row 12 116 JPanel panelButtons = new JPanel(); 117 panelButtons.setLayout(new GridBagLayout()); 118 panelButtons.setBorder(BorderFactory.createTitledBorder("")); 119 panelButtons.setMaximumSize(new Dimension(2000, 200)); 120 121 // row 13 122 addItem(panelButtons, checkDestinationsButton, 0, 0); 123 addItem(panelButtons, saveButton, 1, 0); 124 125 getContentPane().add(p1); 126 getContentPane().add(pane3); 127 getContentPane().add(pFD); 128 getContentPane().add(paneDestinations); 129 getContentPane().add(panelButtons); 130 131 // setup buttons 132 addButtonAction(checkDestinationsButton); 133 addButtonAction(saveButton); 134 135 addRadioButtonAction(destinationsAll); 136 addRadioButtonAction(destinationsInclude); 137 addRadioButtonAction(destinationsExclude); 138 139 // load fields and enable buttons 140 if (_track != null) { 141 _track.addPropertyChangeListener(this); 142 trackName.setText(_track.getName()); 143 onlyCarsWithFD.setSelected(_track.isOnlyCarsWithFinalDestinationEnabled()); 144 pFD.setVisible(_track.isInterchange() || _track.isStaging()); 145 enableButtons(true); 146 } else { 147 enableButtons(false); 148 } 149 150 updateDestinations(); 151 152 locationManager.addPropertyChangeListener(this); 153 154 initMinimumSize(new Dimension(Control.panelWidth400, Control.panelHeight500)); 155 } 156 157 // Save, Delete, Add 158 @Override 159 public void buttonActionPerformed(java.awt.event.ActionEvent ae) { 160 if (_track == null) { 161 return; 162 } 163 if (ae.getSource() == saveButton) { 164 log.debug("track save button activated"); 165 _track.setOnlyCarsWithFinalDestinationEnabled(onlyCarsWithFD.isSelected()); 166 OperationsXml.save(); 167 if (Setup.isCloseWindowOnSaveEnabled()) { 168 dispose(); 169 } 170 } 171 if (ae.getSource() == checkDestinationsButton) { 172 checkDestinationsButton.setEnabled(false); // testing can take awhile, so disable 173 checkDestinationsValid(); 174 } 175 } 176 177 protected void enableButtons(boolean enabled) { 178 saveButton.setEnabled(enabled); 179 checkDestinationsButton.setEnabled(enabled); 180 destinationsAll.setEnabled(enabled); 181 destinationsInclude.setEnabled(enabled); 182 destinationsExclude.setEnabled(enabled); 183 } 184 185 @Override 186 public void radioButtonActionPerformed(java.awt.event.ActionEvent ae) { 187 log.debug("radio button activated"); 188 if (ae.getSource() == destinationsAll) { 189 _track.setDestinationOption(Track.ALL_DESTINATIONS); 190 } 191 if (ae.getSource() == destinationsInclude) { 192 _track.setDestinationOption(Track.INCLUDE_DESTINATIONS); 193 } 194 if (ae.getSource() == destinationsExclude) { 195 _track.setDestinationOption(Track.EXCLUDE_DESTINATIONS); 196 } 197 updateDestinations(); 198 } 199 200 private void updateDestinations() { 201 log.debug("Update destinations"); 202 panelDestinations.removeAll(); 203 if (_track != null) { 204 destinationsAll.setSelected(_track.getDestinationOption().equals(Track.ALL_DESTINATIONS)); 205 destinationsInclude.setSelected(_track.getDestinationOption().equals(Track.INCLUDE_DESTINATIONS)); 206 destinationsExclude.setSelected(_track.getDestinationOption().equals(Track.EXCLUDE_DESTINATIONS)); 207 } 208 List<Location> locations = locationManager.getLocationsByNameList(); 209 for (int i = 0; i < locations.size(); i++) { 210 Location loc = locations.get(i); 211 JCheckBox cb = new JCheckBox(loc.getName()); 212 addItemLeft(panelDestinations, cb, 0, i); 213 cb.setEnabled(!destinationsAll.isSelected()); 214 addCheckBoxAction(cb); 215 if (destinationsAll.isSelected()) { 216 cb.setSelected(true); 217 } else if (_track != null && _track.isDestinationAccepted(loc) 218 ^ _track.getDestinationOption().equals(Track.EXCLUDE_DESTINATIONS)) { 219 cb.setSelected(true); 220 } 221 } 222 panelDestinations.revalidate(); 223 } 224 225 @Override 226 public void checkBoxActionPerformed(java.awt.event.ActionEvent ae) { 227 JCheckBox b = (JCheckBox) ae.getSource(); 228 log.debug("checkbox change {}", b.getText()); 229 if (_track == null) { 230 return; 231 } 232 Location loc = locationManager.getLocationByName(b.getText()); 233 if (loc != null) { 234 if (b.isSelected() ^ _track.getDestinationOption().equals(Track.EXCLUDE_DESTINATIONS)) { 235 _track.addDestination(loc); 236 } else { 237 _track.deleteDestination(loc); 238 } 239 } 240 } 241 242 private void checkDestinationsValid() { 243 SwingUtilities.invokeLater(() -> { 244 if (checkLocationsLoop()) 245 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("OkayMessage")); 246 checkDestinationsButton.setEnabled(true); 247 }); 248 } 249 250 private boolean checkLocationsLoop() { 251 boolean noIssues = true; 252 for (Location destination : locationManager.getLocationsByNameList()) { 253 if (_track.isDestinationAccepted(destination)) { 254 log.debug("Track ({}) accepts destination ({})", _track.getName(), destination.getName()); 255 if (_track.getLocation() == destination) { 256 continue; 257 } 258 // now check to see if the track's rolling stock is accepted by the destination 259 checkTypes: for (String type : InstanceManager.getDefault(CarTypes.class).getNames()) { 260 if (!_track.isTypeNameAccepted(type)) { 261 continue; 262 } 263 if (!destination.acceptsTypeName(type)) { 264 noIssues = false; 265 int response = JmriJOptionPane.showConfirmDialog(this, 266 Bundle.getMessage("WarningDestinationCarType", 267 destination.getName(), type), Bundle.getMessage("WarningCarMayNotMove"), 268 JmriJOptionPane.OK_CANCEL_OPTION); 269 if (response == JmriJOptionPane.OK_OPTION) 270 continue; 271 return false; // done 272 } 273 // now determine if there's a track willing to service car type 274 for (Track track : destination.getTracksList()) { 275 if (track.isTypeNameAccepted(type)) { 276 continue checkTypes; // yes there's a track 277 } 278 } 279 noIssues = false; 280 int response = JmriJOptionPane.showConfirmDialog(this, 281 Bundle.getMessage("WarningDestinationTrackCarType", 282 destination.getName(), type), 283 Bundle.getMessage("WarningCarMayNotMove"), 284 JmriJOptionPane.OK_CANCEL_OPTION); 285 if (response == JmriJOptionPane.OK_OPTION) 286 continue; 287 return false; // done 288 } 289 // now check road names 290 for (String type : InstanceManager.getDefault(CarTypes.class).getNames()) { 291 if (!_track.isTypeNameAccepted(type)) { 292 continue; 293 } 294 checkRoads: for (String road : InstanceManager.getDefault(CarRoads.class).getNames(type)) { 295 if (!_track.isRoadNameAccepted(road)) { 296 continue; 297 } 298 // now determine if there's a track willing to service this road 299 for (Track track : destination.getTracksList()) { 300 if (!track.isTypeNameAccepted(type)) { 301 continue; 302 } 303 if (track.isRoadNameAccepted(road)) { 304 continue checkRoads; // yes there's a track 305 } 306 } 307 noIssues = false; 308 int response = JmriJOptionPane.showConfirmDialog(this, 309 Bundle.getMessage("WarningDestinationTrackCarRoad", 310 destination.getName(), type, road), 311 Bundle.getMessage("WarningCarMayNotMove"), 312 JmriJOptionPane.OK_CANCEL_OPTION); 313 if (response == JmriJOptionPane.OK_OPTION) 314 continue; 315 return false; // done 316 } 317 } 318 // now check load names 319 for (String type : InstanceManager.getDefault(CarTypes.class).getNames()) { 320 if (!_track.isTypeNameAccepted(type)) { 321 continue; 322 } 323 List<String> loads = InstanceManager.getDefault(CarLoads.class).getNames(type); 324 checkLoads: for (String load : loads) { 325 if (!_track.isLoadNameAccepted(load)) { 326 continue; 327 } 328 // now determine if there's a track willing to service this load 329 for (Track track : destination.getTracksList()) { 330 if (track.isLoadNameAccepted(load)) { 331 continue checkLoads; 332 } 333 } 334 noIssues = false; 335 int response = JmriJOptionPane.showConfirmDialog(this, Bundle 336 .getMessage("WarningDestinationTrackCarLoad", destination.getName(), 337 type, load), Bundle.getMessage("WarningCarMayNotMove"), JmriJOptionPane.OK_CANCEL_OPTION); 338 if (response == JmriJOptionPane.OK_OPTION) 339 continue; 340 return false; // done 341 } 342 // now check car type and load combinations 343 checkLoads: for (String load : loads) { 344 if (!_track.isLoadNameAndCarTypeAccepted(load, type)) { 345 continue; 346 } 347 // now determine if there's a track willing to service this load 348 for (Track track : destination.getTracksList()) { 349 if (track.isLoadNameAndCarTypeAccepted(load, type)) { 350 continue checkLoads; 351 } 352 } 353 noIssues = false; 354 int response = JmriJOptionPane.showConfirmDialog(this, Bundle 355 .getMessage("WarningDestinationTrackCarLoad", destination.getName(), 356 type, load), Bundle.getMessage("WarningCarMayNotMove"), JmriJOptionPane.OK_CANCEL_OPTION); 357 if (response == JmriJOptionPane.OK_OPTION) 358 continue; 359 return false; // done 360 } 361 } 362 // now determine if there's a train or trains that can move a car from this track to the destinations 363 // need to check all car types, loads, and roads that this track services 364 Car car = new Car(); 365 car.setLength(Integer.toString(-RollingStock.COUPLERS)); // set car length to net out to zero 366 for (String type : InstanceManager.getDefault(CarTypes.class).getNames()) { 367 if (!_track.isTypeNameAccepted(type)) { 368 continue; 369 } 370 List<String> loads = InstanceManager.getDefault(CarLoads.class).getNames(type); 371 for (String load : loads) { 372 if (!_track.isLoadNameAndCarTypeAccepted(load, type)) { 373 continue; 374 } 375 for (String road : InstanceManager.getDefault(CarRoads.class).getNames(type)) { 376 if (!_track.isRoadNameAccepted(road)) { 377 continue; 378 } 379 // is there a car with this road? 380 boolean foundCar = false; 381 for (RollingStock rs : InstanceManager.getDefault(CarManager.class).getList()) { 382 if (rs.getTypeName().equals(type) && rs.getRoadName().equals(road)) { 383 foundCar = true; 384 break; 385 } 386 } 387 if (!foundCar) { 388 continue; // no car with this road name 389 } 390 391 car.setTypeName(type); 392 car.setRoadName(road); 393 car.setLoadName(load); 394 car.setTrack(_track); 395 car.setFinalDestination(destination); 396 397 // does the destination accept this car? 398 // this checks tracks that have schedules 399 String testDest = "NO_TYPE"; 400 for (Track track : destination.getTracksList()) { 401 if (!track.isTypeNameAccepted(type)) { 402 // already reported if type not accepted 403 continue; 404 } 405 if (track.getScheduleMode() == Track.SEQUENTIAL) { 406 // must test in match mode 407 track.setScheduleMode(Track.MATCH); 408 String itemId = track.getScheduleItemId(); 409 testDest = car.checkDestination(destination, track); 410 track.setScheduleMode(Track.SEQUENTIAL); 411 track.setScheduleItemId(itemId); 412 } else { 413 testDest = car.checkDestination(destination, track); 414 } 415 if (testDest.equals(Track.OKAY)) { 416 break; // done 417 } 418 } 419 420 if (testDest.equals("NO_TYPE")) { 421 continue; 422 } 423 424 if (!testDest.equals(Track.OKAY)) { 425 noIssues = false; 426 int response = JmriJOptionPane.showConfirmDialog(this, Bundle 427 .getMessage("WarningNoTrack", destination.getName(), type, road, load, 428 destination.getName()), Bundle.getMessage("WarningCarMayNotMove"), 429 JmriJOptionPane.OK_CANCEL_OPTION); 430 if (response == JmriJOptionPane.OK_OPTION) 431 continue; 432 return false; // done 433 } 434 435 log.debug("Find train for car type ({}), road ({}), load ({})", type, road, load); 436 437 boolean results = InstanceManager.getDefault(Router.class).setDestination(car, null, null); 438 car.setDestination(null, null); // clear destination if set by router 439 if (!results) { 440 noIssues = false; 441 int response = JmriJOptionPane.showConfirmDialog(this, Bundle 442 .getMessage("WarningNoTrain", type, road, load, 443 destination.getName()), Bundle.getMessage("WarningCarMayNotMove"), 444 JmriJOptionPane.OK_CANCEL_OPTION); 445 if (response == JmriJOptionPane.OK_OPTION) 446 continue; 447 return false; // done 448 } 449 // TODO need to check owners and car built dates 450 } 451 } 452 } 453 } 454 } 455 return noIssues; 456 } 457 458 @Override 459 public void dispose() { 460 if (_track != null) { 461 _track.removePropertyChangeListener(this); 462 } 463 locationManager.removePropertyChangeListener(this); 464 super.dispose(); 465 } 466 467 @Override 468 public void propertyChange(java.beans.PropertyChangeEvent e) { 469 if (Control.SHOW_PROPERTY) { 470 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e 471 .getNewValue()); 472 } 473 if (e.getPropertyName().equals(LocationManager.LISTLENGTH_CHANGED_PROPERTY) || 474 e.getPropertyName().equals(Track.DESTINATIONS_CHANGED_PROPERTY)) { 475 updateDestinations(); 476 } 477 if (e.getPropertyName().equals(Track.ROUTED_CHANGED_PROPERTY)) { 478 onlyCarsWithFD.setSelected((boolean) e.getNewValue()); 479 } 480 } 481 482 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrackDestinationEditFrame.class); 483}