001package jmri.jmrit.operations.rollingstock.cars;
002
003import java.awt.Dimension;
004import java.awt.GridBagLayout;
005import java.util.List;
006import java.util.ResourceBundle;
007
008import javax.swing.*;
009
010import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
011import jmri.InstanceManager;
012import jmri.jmrit.operations.OperationsXml;
013import jmri.jmrit.operations.locations.*;
014import jmri.jmrit.operations.locations.divisions.*;
015import jmri.jmrit.operations.rollingstock.*;
016import jmri.jmrit.operations.rollingstock.cars.tools.*;
017import jmri.jmrit.operations.router.Router;
018import jmri.jmrit.operations.setup.Control;
019import jmri.jmrit.operations.setup.Setup;
020import jmri.jmrit.operations.trains.Train;
021import jmri.jmrit.operations.trains.tools.TrainByCarTypeFrame;
022import jmri.util.swing.JmriJOptionPane;
023
024/**
025 * Frame for user to place car on the layout
026 *
027 * @author Dan Boudreau Copyright (C) 2008, 2010, 2011, 2013, 2014, 2021
028 */
029public class CarSetFrame extends RollingStockSetFrame<Car> {
030
031    protected static final ResourceBundle rb = ResourceBundle
032            .getBundle("jmri.jmrit.operations.rollingstock.cars.JmritOperationsCarsBundle");
033    private static final String IGNORE = "Ignore";
034    private static final String KERNEL = "Kernel";
035    private static final String TIP_IGNORE = "TipIgnore";
036
037    CarManager carManager = InstanceManager.getDefault(CarManager.class);
038    CarLoads carLoads = InstanceManager.getDefault(CarLoads.class);
039
040    public Car _car;
041
042    // combo boxes
043    protected JComboBox<Division> divisionComboBox = InstanceManager.getDefault(DivisionManager.class).getComboBox();
044    protected JComboBox<Location> destReturnWhenEmptyBox = InstanceManager.getDefault(LocationManager.class)
045            .getComboBox();
046    protected JComboBox<Track> trackReturnWhenEmptyBox = new JComboBox<>();
047    protected JComboBox<String> loadReturnWhenEmptyBox = carLoads.getComboBox(null);
048    protected JComboBox<Location> destReturnWhenLoadedBox = InstanceManager.getDefault(LocationManager.class)
049            .getComboBox();
050    protected JComboBox<Track> trackReturnWhenLoadedBox = new JComboBox<>();
051    protected JComboBox<String> loadReturnWhenLoadedBox = carLoads.getComboBox(null);
052    JComboBox<String> loadComboBox = carLoads.getComboBox(null);
053    JComboBox<String> kernelComboBox = InstanceManager.getDefault(KernelManager.class).getComboBox();
054
055    // buttons
056    JButton editDivisionButton = new JButton(Bundle.getMessage("ButtonEdit"));
057    protected JButton editLoadButton = new JButton(Bundle.getMessage("ButtonEdit"));
058    JButton editKernelButton = new JButton(Bundle.getMessage("ButtonEdit"));
059
060    // check boxes
061    public JCheckBox ignoreDivisionCheckBox = new JCheckBox(Bundle.getMessage(IGNORE));
062    public JCheckBox ignoreRWECheckBox = new JCheckBox(Bundle.getMessage(IGNORE));
063    protected JCheckBox autoReturnWhenEmptyTrackCheckBox = new JCheckBox(Bundle.getMessage("Auto"));
064    public JCheckBox ignoreRWLCheckBox = new JCheckBox(Bundle.getMessage(IGNORE));
065    protected JCheckBox autoReturnWhenLoadedTrackCheckBox = new JCheckBox(Bundle.getMessage("Auto"));
066    public JCheckBox ignoreLoadCheckBox = new JCheckBox(Bundle.getMessage(IGNORE));
067    public JCheckBox ignoreKernelCheckBox = new JCheckBox(Bundle.getMessage(IGNORE));
068
069    // Auto checkbox state
070    private static boolean autoReturnWhenEmptyTrackCheckBoxSelected = false;
071    private static boolean autoReturnWhenLoadedTrackCheckBoxSelected = false;
072
073    private static boolean enableDestination = false;
074    
075    private String _help = "package.jmri.jmrit.operations.Operations_CarsSet";
076
077    public CarSetFrame() {
078        super(Bundle.getMessage("TitleCarSet"));
079    }
080    
081    public void initComponents(String help) {
082        _help = help;
083        initComponents();
084    }
085
086    @Override
087    public void initComponents() {
088        super.initComponents();
089
090        // build menu
091        JMenuBar menuBar = new JMenuBar();
092        JMenu toolMenu = new JMenu(Bundle.getMessage("MenuTools"));
093        toolMenu.add(new EnableDestinationAction(this));
094        toolMenu.add(new CarRoutingReportAction(this, true)); // preview
095        toolMenu.add(new CarRoutingReportAction(this, false)); // print
096        menuBar.add(toolMenu);
097        setJMenuBar(menuBar);
098        addHelpMenu(_help, true); // NOI18N
099
100        // initial caps for some languages i.e. German
101        editLoadButton.setToolTipText(
102                Bundle.getMessage("TipAddDeleteReplace", Bundle.getMessage("load")));
103        editKernelButton.setToolTipText(Bundle.getMessage("TipAddDeleteReplace",
104                Bundle.getMessage(KERNEL).toLowerCase()));
105
106        // optional panel load, return when empty, return when loaded, division, and
107        // kernel
108        paneOptional.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("BorderLayoutOptional")));
109        pOptional.setLayout(new BoxLayout(pOptional, BoxLayout.Y_AXIS));
110
111        // add load fields
112        JPanel pLoad = new JPanel();
113        pLoad.setLayout(new GridBagLayout());
114        pLoad.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Load")));
115        addItemLeft(pLoad, ignoreLoadCheckBox, 1, 0);
116        loadComboBox.setName("loadComboBox");
117        addItem(pLoad, loadComboBox, 2, 0);
118        addItem(pLoad, editLoadButton, 3, 0);
119        pOptional.add(pLoad);
120
121        // row 5
122        JPanel pReturnWhenEmpty = new JPanel();
123        pReturnWhenEmpty.setLayout(new GridBagLayout());
124        pReturnWhenEmpty.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("BorderLayoutReturnWhenEmpty")));
125        addItem(pReturnWhenEmpty, new JLabel(Bundle.getMessage("Location")), 1, 0);
126        addItem(pReturnWhenEmpty, new JLabel(Bundle.getMessage("Track")), 2, 0);
127        addItem(pReturnWhenEmpty, new JLabel(Bundle.getMessage("Load")), 3, 0);
128        addItemLeft(pReturnWhenEmpty, ignoreRWECheckBox, 0, 1);
129        addItem(pReturnWhenEmpty, destReturnWhenEmptyBox, 1, 1);
130        addItem(pReturnWhenEmpty, trackReturnWhenEmptyBox, 2, 1);
131        addItem(pReturnWhenEmpty, loadReturnWhenEmptyBox, 3, 1);
132        addItem(pReturnWhenEmpty, autoReturnWhenEmptyTrackCheckBox, 4, 1);
133        pOptional.add(pReturnWhenEmpty);
134
135        // row 6
136        JPanel pReturnWhenLoaded = new JPanel();
137        pReturnWhenLoaded.setLayout(new GridBagLayout());
138        pReturnWhenLoaded
139                .setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("BorderLayoutReturnWhenLoaded")));
140        addItem(pReturnWhenLoaded, new JLabel(Bundle.getMessage("Location")), 1, 0);
141        addItem(pReturnWhenLoaded, new JLabel(Bundle.getMessage("Track")), 2, 0);
142        addItem(pReturnWhenLoaded, new JLabel(Bundle.getMessage("Load")), 3, 0);
143        addItemLeft(pReturnWhenLoaded, ignoreRWLCheckBox, 0, 1);
144        addItem(pReturnWhenLoaded, destReturnWhenLoadedBox, 1, 1);
145        addItem(pReturnWhenLoaded, trackReturnWhenLoadedBox, 2, 1);
146        addItem(pReturnWhenLoaded, loadReturnWhenLoadedBox, 3, 1);
147        addItem(pReturnWhenLoaded, autoReturnWhenLoadedTrackCheckBox, 4, 1);
148        pOptional.add(pReturnWhenLoaded);
149
150        // division field
151        JPanel pDivision = new JPanel();
152        pDivision.setLayout(new GridBagLayout());
153        pDivision.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("HomeDivision")));
154        addItemLeft(pDivision, ignoreDivisionCheckBox, 1, 0);
155        addItem(pDivision, divisionComboBox, 2, 0);
156        addItem(pDivision, editDivisionButton, 3, 0);
157        pOptional.add(pDivision);
158
159        // add kernel fields
160        JPanel pKernel = new JPanel();
161        pKernel.setLayout(new GridBagLayout());
162        pKernel.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage(KERNEL)));
163        addItemLeft(pKernel, ignoreKernelCheckBox, 1, 0);
164        kernelComboBox.setName("kernelComboBox"); // NOI18N for UI Test
165        addItem(pKernel, kernelComboBox, 2, 0);
166        addItem(pKernel, editKernelButton, 3, 0);
167        pOptional.add(pKernel);
168
169        // don't show ignore checkboxes
170        ignoreDivisionCheckBox.setVisible(false);
171        ignoreRWECheckBox.setVisible(false);
172        ignoreRWLCheckBox.setVisible(false);
173        ignoreLoadCheckBox.setVisible(false);
174        ignoreKernelCheckBox.setVisible(false);
175
176        autoReturnWhenEmptyTrackCheckBox.setSelected(autoReturnWhenEmptyTrackCheckBoxSelected);
177        autoReturnWhenLoadedTrackCheckBox.setSelected(autoReturnWhenLoadedTrackCheckBoxSelected);
178
179        // setup combobox
180        addComboBoxAction(destReturnWhenEmptyBox);
181        addComboBoxAction(destReturnWhenLoadedBox);
182        addComboBoxAction(loadComboBox);
183        addComboBoxAction(divisionComboBox);
184
185        // setup button
186        addButtonAction(editLoadButton);
187        addButtonAction(editDivisionButton);
188        addButtonAction(editKernelButton);
189
190        // setup checkboxes
191        addCheckBoxAction(ignoreRWECheckBox);
192        addCheckBoxAction(ignoreRWLCheckBox);
193        addCheckBoxAction(autoReturnWhenEmptyTrackCheckBox);
194        addCheckBoxAction(autoReturnWhenLoadedTrackCheckBox);
195        addCheckBoxAction(ignoreLoadCheckBox);
196        addCheckBoxAction(ignoreDivisionCheckBox);
197        addCheckBoxAction(ignoreKernelCheckBox);
198
199        // tool tips
200        ignoreRWECheckBox.setToolTipText(Bundle.getMessage(TIP_IGNORE));
201        ignoreRWLCheckBox.setToolTipText(Bundle.getMessage(TIP_IGNORE));
202        ignoreLoadCheckBox.setToolTipText(Bundle.getMessage(TIP_IGNORE));
203        ignoreDivisionCheckBox.setToolTipText(Bundle.getMessage(TIP_IGNORE));
204        ignoreKernelCheckBox.setToolTipText(Bundle.getMessage(TIP_IGNORE));
205        outOfServiceCheckBox.setToolTipText(Bundle.getMessage("TipCarOutOfService"));
206        autoReturnWhenEmptyTrackCheckBox.setToolTipText(Bundle.getMessage("rsTipAutoTrack"));
207        autoReturnWhenLoadedTrackCheckBox.setToolTipText(Bundle.getMessage("rsTipAutoTrack"));
208
209        // get notified if combo box gets modified
210        carLoads.addPropertyChangeListener(this);
211        carManager.addPropertyChangeListener(this);
212        InstanceManager.getDefault(KernelManager.class).addPropertyChangeListener(this);
213        InstanceManager.getDefault(DivisionManager.class).addPropertyChangeListener(this);
214
215        initMinimumSize(new Dimension(Control.panelWidth500, Control.panelHeight500));
216    }
217
218    public void load(Car car) {
219        _car = car;
220        super.load(car);
221        updateLoadComboBox();
222        updateRweLoadComboBox();
223        updateRwlLoadComboBox();
224        updateDivisionComboBox();
225        updateKernelComboBox();
226    }
227
228    @Override
229    protected ResourceBundle getRb() {
230        return rb;
231    }
232
233    @Override
234    protected void updateComboBoxes() {
235        super.updateComboBoxes();
236
237        locationManager.updateComboBox(destReturnWhenEmptyBox);
238        locationManager.updateComboBox(destReturnWhenLoadedBox);
239
240        updateFinalDestinationComboBoxes();
241        updateReturnWhenEmptyComboBoxes();
242        updateReturnWhenLoadedComboBoxes();
243    }
244
245    @Override
246    protected void enableComponents(boolean enabled) {
247        // If routing is disabled, the RWE and Final Destination fields do not work
248        if (!Setup.isCarRoutingEnabled()) {
249            ignoreRWECheckBox.setSelected(true);
250            ignoreRWLCheckBox.setSelected(true);
251            ignoreFinalDestinationCheckBox.setSelected(true);
252            ignoreDivisionCheckBox.setSelected(true);
253        }
254
255        super.enableComponents(enabled);
256
257        ignoreRWECheckBox.setEnabled(Setup.isCarRoutingEnabled() && enabled);
258        destReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected() && enabled);
259        trackReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected() && enabled);
260        loadReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected() && enabled);
261        autoReturnWhenEmptyTrackCheckBox.setEnabled(!ignoreRWECheckBox.isSelected() && enabled);
262
263        ignoreRWLCheckBox.setEnabled(Setup.isCarRoutingEnabled() && enabled);
264        destReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected() && enabled);
265        trackReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected() && enabled);
266        loadReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected() && enabled);
267        autoReturnWhenLoadedTrackCheckBox.setEnabled(!ignoreRWLCheckBox.isSelected() && enabled);
268
269        ignoreLoadCheckBox.setEnabled(enabled);
270        loadComboBox.setEnabled(!ignoreLoadCheckBox.isSelected() && enabled);
271        editLoadButton.setEnabled(!ignoreLoadCheckBox.isSelected() && enabled && _car != null);
272        
273        ignoreDivisionCheckBox.setEnabled(Setup.isCarRoutingEnabled() && enabled);
274        divisionComboBox.setEnabled(!ignoreDivisionCheckBox.isSelected() && enabled);
275        editDivisionButton.setEnabled(!ignoreDivisionCheckBox.isSelected() && enabled && _car != null);
276
277        ignoreKernelCheckBox.setEnabled(enabled);
278        kernelComboBox.setEnabled(!ignoreKernelCheckBox.isSelected() && enabled);
279        editKernelButton.setEnabled(!ignoreKernelCheckBox.isSelected() && enabled && _car != null);
280
281        enableDestinationFields(enabled);
282    }
283
284    private void enableDestinationFields(boolean enabled) {
285        // if car in a built train, enable destination fields
286        boolean enableDest = enableDestination ||
287                destinationBox.getSelectedItem() != null ||
288                (_car != null && _car.getTrain() != null && _car.getTrain().isBuilt());
289
290        destinationBox.setEnabled(!ignoreDestinationCheckBox.isSelected() && enableDest && enabled);
291        trackDestinationBox.setEnabled(!ignoreDestinationCheckBox.isSelected() && enableDest && enabled);
292        autoDestinationTrackCheckBox.setEnabled(!ignoreDestinationCheckBox.isSelected() && enableDest && enabled);
293    }
294
295    // combo boxes
296    @Override
297    public void comboBoxActionPerformed(java.awt.event.ActionEvent ae) {
298        super.comboBoxActionPerformed(ae);
299        if (ae.getSource() == finalDestinationBox) {
300            updateFinalDestinationTrack();
301        }
302        if (ae.getSource() == destReturnWhenEmptyBox) {
303            updateReturnWhenEmptyTrack();
304        }
305        if (ae.getSource() == destReturnWhenLoadedBox) {
306            updateReturnWhenLoadedTrack();
307        }
308    }
309
310    CarLoadEditFrame lef;
311    CarAttributeEditFrame cef;
312    DivisionEditFrame def;    
313
314    @Override
315    public void buttonActionPerformed(java.awt.event.ActionEvent ae) {
316        super.buttonActionPerformed(ae);
317        if (ae.getSource() == editLoadButton && _car != null) {
318            if (lef != null) {
319                lef.dispose();
320            }
321            lef = new CarLoadEditFrame();
322            lef.initComponents(_car.getTypeName(), (String) loadComboBox.getSelectedItem());
323        }
324        if (ae.getSource() == editKernelButton) {
325            if (cef != null) {
326                cef.dispose();
327            }
328            cef = new CarAttributeEditFrame();
329            cef.addPropertyChangeListener(this);
330            cef.initComponents(CarAttributeEditFrame.KERNEL, (String) kernelComboBox.getSelectedItem());
331        }
332        if (ae.getSource() == editDivisionButton) {
333            if (def != null) {
334                def.dispose();
335            }
336            def = new DivisionEditFrame((Division) divisionComboBox.getSelectedItem());
337        }
338    }
339
340    @Override
341    protected boolean save() {
342        if (change(_car)) {
343            OperationsXml.save();
344            return true;
345        }
346        return false;
347    }
348
349    protected boolean askKernelChange = true;
350
351    @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "GUI ease of use")
352    protected boolean change(Car car) {
353        // save the auto button
354        autoReturnWhenEmptyTrackCheckBoxSelected = autoReturnWhenEmptyTrackCheckBox.isSelected();
355        autoReturnWhenLoadedTrackCheckBoxSelected = autoReturnWhenLoadedTrackCheckBox.isSelected();
356
357        // save car's track in case there's a schedule
358        Track saveTrack = car.getTrack();
359        // update location
360        if (!changeLocation(car)) {
361            return false;
362        }
363        // car load
364        setCarLoad(car);
365        // set final destination fields before destination in case there's a schedule at
366        // destination
367        if (!setCarFinalDestination(car)) {
368            return false;
369        }
370        // division
371        if (!ignoreDivisionCheckBox.isSelected()) {
372            car.setDivision((Division) divisionComboBox.getSelectedItem());
373        }
374        // kernel
375        setCarKernel(car);
376        if (!super.change(car)) {
377            return false;
378        }
379        // return when empty fields
380        if (!setCarRWE(car)) {
381            return false;
382        }
383        // return when loaded fields
384        if (!setCarRWL(car)) {
385            return false;
386        }
387        // check to see if there's a schedule when placing the car at a spur
388        if (!applySchedule(car, saveTrack)) {
389            return false;
390        }
391        // determine if train services this car's load
392        if (!checkTrainLoad(car)) {
393            return false;
394        }
395        // determine if train's route can service car
396        if (!checkTrainRoute(car)) {
397            return false;
398        }
399        checkTrain(car);
400        // is this car part of a kernel?
401        if (askKernelChange && car.getKernel() != null) {
402            List<Car> list = car.getKernel().getCars();
403            if (list.size() > 1) {
404                if (JmriJOptionPane.showConfirmDialog(this,
405                        Bundle.getMessage("carInKernel", car.toString()),
406                        Bundle.getMessage("carPartKernel", car.getKernelName()),
407                        JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) {
408                    if (!updateGroup(list)) {
409                        return false;
410                    }
411                } else if (outOfServiceCheckBox.isSelected()) {
412                    car.setKernel(null); // don't leave car in kernel if out of service
413                }
414            }
415        }
416        return true;
417    }
418    
419    private void setCarLoad(Car car) {
420        if (!ignoreLoadCheckBox.isSelected() && loadComboBox.getSelectedItem() != null) {
421            String load = (String) loadComboBox.getSelectedItem();
422            if (!car.getLoadName().equals(load)) {
423                if (carLoads.containsName(car.getTypeName(), load)) {
424                    car.setLoadName(load);
425                    car.setWait(0); // car could be at spur with schedule
426                    car.setScheduleItemId(Car.NONE);
427                    updateComboBoxesLoadChange();
428                } else {
429                    JmriJOptionPane.showMessageDialog(this,
430                            Bundle.getMessage("carLoadNotValid", load, car.getTypeName()),
431                            Bundle.getMessage("carCanNotChangeLoad"), JmriJOptionPane.WARNING_MESSAGE);
432                }
433            }
434        }
435    }
436    
437    private boolean setCarFinalDestination(Car car) {
438        if (!ignoreFinalDestinationCheckBox.isSelected()) {
439            if (finalDestinationBox.getSelectedItem() == null) {
440                car.setFinalDestination(null);
441                car.setFinalDestinationTrack(null);
442            } else {
443                Track finalDestTrack = null;
444                if (finalDestTrackBox.getSelectedItem() != null) {
445                    finalDestTrack = (Track) finalDestTrackBox.getSelectedItem();
446                }
447                if (finalDestTrack != null &&
448                        car.getFinalDestinationTrack() != finalDestTrack &&
449                        finalDestTrack.isStaging()) {
450                    log.debug("Destination track ({}) is staging", finalDestTrack.getName());
451                    JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("rsDoNotSelectStaging"),
452                            Bundle.getMessage("rsCanNotFinal"), JmriJOptionPane.ERROR_MESSAGE);
453                    return false;
454                }
455                car.setFinalDestination((Location) finalDestinationBox.getSelectedItem());
456                car.setFinalDestinationTrack(finalDestTrack);
457                String status = getTestCar(car, car.getLoadName())
458                        .checkDestination(car.getFinalDestination(), finalDestTrack);
459                if (!status.equals(Track.OKAY)) {
460                    JmriJOptionPane.showMessageDialog(this,
461                            Bundle.getMessage("rsCanNotFinalMsg", car.toString(), status),
462                            Bundle.getMessage("rsCanNotFinal"), JmriJOptionPane.WARNING_MESSAGE);
463                    return false;
464                } else {
465                    // check to see if car can be routed to final destination
466                    Router router = InstanceManager.getDefault(Router.class);
467                    if (!router.isCarRouteable(car, null, car.getFinalDestination(), finalDestTrack, null)) {
468                        JmriJOptionPane.showMessageDialog(this,
469                                Bundle.getMessage("rsCanNotRouteMsg", car.toString(),
470                                        car.getLocationName(), car.getTrackName(), car.getFinalDestinationName(),
471                                        car.getFinalDestinationTrackName()),
472                                Bundle.getMessage("rsCanNotFinal"), JmriJOptionPane.WARNING_MESSAGE);
473                        return false;
474                    }
475                }
476            }
477        }
478        return true;
479    }
480    
481    private void setCarKernel(Car car) {
482        if (!ignoreKernelCheckBox.isSelected() && kernelComboBox.getSelectedItem() != null) {
483            if (kernelComboBox.getSelectedItem().equals(RollingStockManager.NONE)) {
484                car.setKernel(null);
485                if (!car.isPassenger()) {
486                    car.setBlocking(Car.DEFAULT_BLOCKING_ORDER);
487                }
488            } else if (!car.getKernelName().equals(kernelComboBox.getSelectedItem())) {
489                car.setKernel(InstanceManager.getDefault(KernelManager.class).getKernelByName((String) kernelComboBox.getSelectedItem()));
490                // if car has FRED or is caboose make lead
491                if (car.hasFred() || car.isCaboose()) {
492                    car.getKernel().setLead(car);
493                }
494                car.setBlocking(car.getKernel().getSize());
495            }
496        }
497    }
498    
499    private boolean setCarRWE(Car car) {
500        if (!ignoreRWECheckBox.isSelected()) {
501            // check that RWE load is valid for this car's type
502            if (carLoads.getNames(car.getTypeName()).contains(loadReturnWhenEmptyBox.getSelectedItem())) {
503                car.setReturnWhenEmptyLoadName((String) loadReturnWhenEmptyBox.getSelectedItem());
504            } else {
505                log.debug("Car ({}) type ({}) doesn't support RWE load ({})", car, car.getTypeName(),
506                        loadReturnWhenEmptyBox.getSelectedItem());
507                JmriJOptionPane.showMessageDialog(this,
508                        Bundle.getMessage("carLoadNotValid",
509                                loadReturnWhenEmptyBox.getSelectedItem(), car.getTypeName()),
510                        Bundle.getMessage("carCanNotChangeRweLoad"), JmriJOptionPane.WARNING_MESSAGE);
511            }
512            if (destReturnWhenEmptyBox.getSelectedItem() == null) {
513                car.setReturnWhenEmptyDestination(null);
514                car.setReturnWhenEmptyDestTrack(null);
515            } else {
516                Location locationRWE = (Location) destReturnWhenEmptyBox.getSelectedItem();
517                if (trackReturnWhenEmptyBox.getSelectedItem() != null) {
518                    Track trackRWE = (Track) trackReturnWhenEmptyBox.getSelectedItem();
519                    // warn user if they selected a staging track
520                    if (trackRWE != null && trackRWE.isStaging()) {
521                        log.debug("Return when empty track ({}) is staging", trackRWE.getName());
522                        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("rsDoNotSelectStaging"),
523                                Bundle.getMessage("carCanNotRWE"), JmriJOptionPane.ERROR_MESSAGE);
524                        return false;
525                    }
526                    // use a test car with a load of "RWE" and no length
527                    String status = getTestCar(car, car.getReturnWhenEmptyLoadName()).checkDestination(locationRWE,
528                            trackRWE);
529                    if (!status.equals(Track.OKAY)) {
530                        JmriJOptionPane.showMessageDialog(this,
531                                Bundle.getMessage("carCanNotRWEMsg", car.toString(), locationRWE,
532                                        trackRWE, status),
533                                Bundle.getMessage("carCanNotRWE"), JmriJOptionPane.WARNING_MESSAGE);
534                    }
535                    car.setReturnWhenEmptyDestTrack(trackRWE);
536                } else {
537                    car.setReturnWhenEmptyDestTrack(null);
538                }
539                car.setReturnWhenEmptyDestination(locationRWE);
540            }
541        }
542        return true;
543    }
544    
545    private boolean setCarRWL(Car car) {
546        if (!ignoreRWLCheckBox.isSelected()) {
547            // check that RWL load is valid for this car's type
548            if (carLoads.getNames(car.getTypeName()).contains(loadReturnWhenLoadedBox.getSelectedItem())) {
549                car.setReturnWhenLoadedLoadName((String) loadReturnWhenLoadedBox.getSelectedItem());
550            } else {
551                log.debug("Car ({}) type ({}) doesn't support RWL load ({})", car, car.getTypeName(),
552                        loadReturnWhenLoadedBox.getSelectedItem());
553                JmriJOptionPane.showMessageDialog(this,
554                        Bundle.getMessage("carLoadNotValid",
555                                loadReturnWhenEmptyBox.getSelectedItem(), car.getTypeName()),
556                        Bundle.getMessage("carCanNotChangeRwlLoad"), JmriJOptionPane.WARNING_MESSAGE);
557            }
558            if (destReturnWhenLoadedBox.getSelectedItem() == null) {
559                car.setReturnWhenLoadedDestination(null);
560                car.setReturnWhenLoadedDestTrack(null);
561            } else {
562                Location locationRWL = (Location) destReturnWhenLoadedBox.getSelectedItem();
563                if (trackReturnWhenLoadedBox.getSelectedItem() != null) {
564                    Track trackRWL = (Track) trackReturnWhenLoadedBox.getSelectedItem();
565                    // warn user if they selected a staging track
566                    if (trackRWL != null && trackRWL.isStaging()) {
567                        log.debug("Return when loaded track ({}) is staging", trackRWL.getName());
568                        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("rsDoNotSelectStaging"),
569                                Bundle.getMessage("carCanNotRWL"), JmriJOptionPane.ERROR_MESSAGE);
570                        return false;
571                    }
572                    // use a test car with a load of "RWL" and no length
573                    String status = getTestCar(car, car.getReturnWhenLoadedLoadName()).checkDestination(locationRWL,
574                            trackRWL);
575                    if (!status.equals(Track.OKAY)) {
576                        JmriJOptionPane.showMessageDialog(this,
577                                Bundle.getMessage("carCanNotRWLMsg", car.toString(), locationRWL,
578                                        trackRWL, status),
579                                Bundle.getMessage("carCanNotRWL"), JmriJOptionPane.WARNING_MESSAGE);
580                    }
581                    car.setReturnWhenLoadedDestTrack(trackRWL);
582                } else {
583                    car.setReturnWhenLoadedDestTrack(null);
584                }
585                car.setReturnWhenLoadedDestination(locationRWL);
586            }
587        }
588        return true;
589    }
590    
591    private boolean applySchedule(Car car, Track saveTrack) {
592        if (!ignoreLocationCheckBox.isSelected() &&
593                trackLocationBox.getSelectedItem() != null &&
594                saveTrack != trackLocationBox.getSelectedItem()) {
595            Track track = (Track) trackLocationBox.getSelectedItem();
596            if (track.getSchedule() != null) {
597                if (JmriJOptionPane
598                        .showConfirmDialog(this,
599                                Bundle.getMessage("rsDoYouWantSchedule", car.toString()),
600                                Bundle.getMessage("rsSpurHasSchedule", track.getName(),
601                                        track.getScheduleName()),
602                                JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) {
603                    String results = track.checkSchedule(car);
604                    if (!results.equals(Track.OKAY)) {
605                        JmriJOptionPane.showMessageDialog(this,
606                                Bundle.getMessage("rsNotAbleToApplySchedule", results),
607                                Bundle.getMessage("rsApplyingScheduleFailed"), JmriJOptionPane.ERROR_MESSAGE);
608                        // restore previous location and track so we'll ask to test schedule again
609                        if (saveTrack != null) {
610                            car.setLocation(saveTrack.getLocation(), saveTrack);
611                        } else {
612                            car.setLocation(null, null);
613                        }
614                        return false;
615                    }
616                    // now apply schedule to car
617                    track.scheduleNext(car);
618                    car.loadNext(track);
619                }
620            }
621        }
622        return true;
623    }
624    
625    private boolean checkTrainLoad(Car car) {
626        if (car.getTrain() != null) {
627            Train train = car.getTrain();
628            if (!train.isLoadNameAccepted(car.getLoadName(), car.getTypeName())) {
629                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("carTrainNotServLoad",
630                        car.getLoadName(), train.getName()), Bundle.getMessage("rsNotMove"), JmriJOptionPane.ERROR_MESSAGE);
631                // prevent rs from being picked up and delivered
632                setRouteLocationAndDestination(car, train, null, null);
633                return false;
634            }
635        }
636        return true;
637    }
638
639    TrainByCarTypeFrame tctf = null;
640    
641    private boolean checkTrainRoute(Car car) {
642        if (car.getTrain() != null) {
643            Train train = car.getTrain();
644            if (car.getLocation() != null && car.getDestination() != null && !train.isServiceable(car)) {
645                JmriJOptionPane.showMessageDialog(this,
646                        Bundle.getMessage("carTrainNotService", car.toString(), train.getName()),
647                        Bundle.getMessage("rsNotMove"), JmriJOptionPane.ERROR_MESSAGE);
648                // show the train's route and car location
649                if (tctf != null) {
650                    tctf.dispose();
651                }
652                tctf = new TrainByCarTypeFrame(car);
653                // prevent rs from being picked up and delivered
654                setRouteLocationAndDestination(car, train, null, null);
655                return false;
656            }
657        }
658        return true;
659    }
660
661    /**
662     * Update locations if load changes. New load could change which track are
663     * allowed if auto selected.
664     */
665    protected void updateComboBoxesLoadChange() {
666        if (autoTrackCheckBox.isSelected()) {
667            updateLocationTrackComboBox();
668        }
669        if (autoDestinationTrackCheckBox.isSelected()) {
670            updateDestinationTrackComboBox();
671        }
672        if (autoFinalDestTrackCheckBox.isSelected()) {
673            updateFinalDestinationTrack();
674        }
675    }
676
677    @Override
678    protected boolean updateGroup(List<Car> list) {
679        for (Car car : list) {
680            if (car == _car) {
681                continue;
682            }
683            // make all cars in kernel the same
684            if (!ignoreRWECheckBox.isSelected()) {
685                car.setReturnWhenEmptyDestination(_car.getReturnWhenEmptyDestination());
686                car.setReturnWhenEmptyDestTrack(_car.getReturnWhenEmptyDestTrack());
687            }
688            if (!ignoreRWLCheckBox.isSelected()) {
689                car.setReturnWhenLoadedDestination(_car.getReturnWhenLoadedDestination());
690                car.setReturnWhenLoadedDestTrack(_car.getReturnWhenLoadedDestTrack());
691            }
692            if (!ignoreFinalDestinationCheckBox.isSelected()) {
693                car.setFinalDestination(_car.getFinalDestination());
694                car.setFinalDestinationTrack(_car.getFinalDestinationTrack());
695            }
696            // update car load
697            if (!ignoreLoadCheckBox.isSelected() && carLoads.containsName(car.getTypeName(), _car.getLoadName())) {
698                car.setLoadName(_car.getLoadName());
699                car.setWait(0); // car could be at spur with schedule
700                car.setScheduleItemId(Car.NONE);
701            }
702            // update kernel
703            if (!ignoreKernelCheckBox.isSelected()) {
704                car.setKernel(_car.getKernel());
705            }
706            // update division
707            if (!ignoreDivisionCheckBox.isSelected()) {
708                car.setDivision(_car.getDivision());
709            }
710        }
711        return super.updateGroup(list);
712    }
713
714    @Override
715    public void checkBoxActionPerformed(java.awt.event.ActionEvent ae) {
716        super.checkBoxActionPerformed(ae);
717        if (ae.getSource() == autoFinalDestTrackCheckBox) {
718            updateFinalDestinationTrack();
719        }
720        if (ae.getSource() == autoReturnWhenEmptyTrackCheckBox) {
721            updateReturnWhenEmptyTrack();
722        }
723        if (ae.getSource() == autoReturnWhenLoadedTrackCheckBox) {
724            updateReturnWhenLoadedTrack();
725        }
726        if (ae.getSource() == autoTrainCheckBox) {
727            updateTrainComboBox();
728        }
729        if (ae.getSource() == ignoreRWECheckBox) {
730            destReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected());
731            trackReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected());
732            loadReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected());
733            autoReturnWhenEmptyTrackCheckBox.setEnabled(!ignoreRWECheckBox.isSelected());
734        }
735        if (ae.getSource() == ignoreRWLCheckBox) {
736            destReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected());
737            trackReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected());
738            loadReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected());
739            autoReturnWhenLoadedTrackCheckBox.setEnabled(!ignoreRWLCheckBox.isSelected());
740        }
741        if (ae.getSource() == ignoreLoadCheckBox) {
742            loadComboBox.setEnabled(!ignoreLoadCheckBox.isSelected());
743            editLoadButton.setEnabled(!ignoreLoadCheckBox.isSelected() && _car != null);
744        }
745        if (ae.getSource() == ignoreDivisionCheckBox) {
746            divisionComboBox.setEnabled(!ignoreDivisionCheckBox.isSelected());
747            editDivisionButton.setEnabled(!ignoreDivisionCheckBox.isSelected());
748        }
749        if (ae.getSource() == ignoreKernelCheckBox) {
750            kernelComboBox.setEnabled(!ignoreKernelCheckBox.isSelected());
751            editKernelButton.setEnabled(!ignoreKernelCheckBox.isSelected());
752        }
753    }
754
755    protected void updateReturnWhenEmptyComboBoxes() {
756        if (_car != null) {
757            log.debug("Updating return when empty for car ({})", _car);
758            destReturnWhenEmptyBox.setSelectedItem(_car.getReturnWhenEmptyDestination());
759        }
760        updateReturnWhenEmptyTrack();
761    }
762
763    protected void updateReturnWhenEmptyTrack() {
764        if (destReturnWhenEmptyBox.getSelectedItem() == null) {
765            trackReturnWhenEmptyBox.removeAllItems();
766        } else {
767            log.debug("CarSetFrame sees return when empty: {}", destReturnWhenEmptyBox.getSelectedItem());
768            Location loc = (Location) destReturnWhenEmptyBox.getSelectedItem();
769            loc.updateComboBox(trackReturnWhenEmptyBox, getTestCar(_car, _car.getReturnWhenEmptyLoadName()),
770                    autoReturnWhenEmptyTrackCheckBox.isSelected(), true);
771            if (_car != null &&
772                    _car.getReturnWhenEmptyDestination() != null &&
773                    _car.getReturnWhenEmptyDestination().equals(loc) &&
774                    _car.getReturnWhenEmptyDestTrack() != null) {
775                trackReturnWhenEmptyBox.setSelectedItem(_car.getReturnWhenEmptyDestTrack());
776            }
777        }
778    }
779
780    protected void updateReturnWhenLoadedComboBoxes() {
781        if (_car != null) {
782            log.debug("Updating return when loaded for car ({})", _car);
783            destReturnWhenLoadedBox.setSelectedItem(_car.getReturnWhenLoadedDestination());
784        }
785        updateReturnWhenLoadedTrack();
786    }
787
788    protected void updateReturnWhenLoadedTrack() {
789        if (destReturnWhenLoadedBox.getSelectedItem() == null) {
790            trackReturnWhenLoadedBox.removeAllItems();
791        } else {
792            log.debug("CarSetFrame sees return when empty: {}", destReturnWhenLoadedBox.getSelectedItem());
793            Location loc = (Location) destReturnWhenLoadedBox.getSelectedItem();
794            loc.updateComboBox(trackReturnWhenLoadedBox, getTestCar(_car, _car.getReturnWhenLoadedLoadName()),
795                    autoReturnWhenLoadedTrackCheckBox.isSelected(), true);
796            if (_car != null &&
797                    _car.getReturnWhenLoadedDestination() != null &&
798                    _car.getReturnWhenLoadedDestination().equals(loc) &&
799                    _car.getReturnWhenLoadedDestTrack() != null) {
800                trackReturnWhenLoadedBox.setSelectedItem(_car.getReturnWhenLoadedDestTrack());
801            }
802        }
803    }
804
805    protected void updateFinalDestinationComboBoxes() {
806        if (_car != null) {
807            log.debug("Updating final destinations for car ({})", _car);
808            finalDestinationBox.setSelectedItem(_car.getFinalDestination());
809        }
810        updateFinalDestinationTrack();
811    }
812
813    protected void updateFinalDestinationTrack() {
814        if (finalDestinationBox.getSelectedItem() == null) {
815            finalDestTrackBox.removeAllItems();
816        } else {
817            log.debug("CarSetFrame sees final destination: {}", finalDestinationBox.getSelectedItem());
818            Location l = (Location) finalDestinationBox.getSelectedItem();
819            l.updateComboBox(finalDestTrackBox, _car, autoFinalDestTrackCheckBox.isSelected(), true);
820            if (_car != null &&
821                    _car.getFinalDestination() != null &&
822                    _car.getFinalDestination().equals(l) &&
823                    _car.getFinalDestinationTrack() != null) {
824                finalDestTrackBox.setSelectedItem(_car.getFinalDestinationTrack());
825            }
826        }
827    }
828
829    protected void updateLoadComboBox() {
830        if (_car != null) {
831            log.debug("Updating load box for car ({})", _car);
832            carLoads.updateComboBox(_car.getTypeName(), loadComboBox);
833            loadComboBox.setSelectedItem(_car.getLoadName());
834        }
835    }
836
837    protected void updateRweLoadComboBox() {
838        if (_car != null) {
839            log.debug("Updating RWE load box for car ({})", _car);
840            carLoads.updateRweComboBox(_car.getTypeName(), loadReturnWhenEmptyBox);
841            loadReturnWhenEmptyBox.setSelectedItem(_car.getReturnWhenEmptyLoadName());
842        }
843    }
844
845    protected void updateRwlLoadComboBox() {
846        if (_car != null) {
847            log.debug("Updating RWL load box for car ({})", _car);
848            carLoads.updateRwlComboBox(_car.getTypeName(), loadReturnWhenLoadedBox);
849            loadReturnWhenLoadedBox.setSelectedItem(_car.getReturnWhenLoadedLoadName());
850        }
851    }
852
853    protected void updateKernelComboBox() {
854        InstanceManager.getDefault(KernelManager.class).updateComboBox(kernelComboBox);
855        if (_car != null) {
856            kernelComboBox.setSelectedItem(_car.getKernelName());
857        }
858    }
859    
860    protected void updateDivisionComboBox() {
861        InstanceManager.getDefault(DivisionManager.class).updateComboBox(divisionComboBox);
862        if (_car != null) {
863            divisionComboBox.setSelectedItem(_car.getDivision());
864        }
865    }
866
867    @Override
868    protected void updateTrainComboBox() {
869        log.debug("update train combo box");
870        if (_car != null && autoTrainCheckBox.isSelected()) {
871            log.debug("Updating train box for car ({})", _car);
872            trainManager.updateTrainComboBox(trainBox, _car);
873        } else {
874            trainManager.updateTrainComboBox(trainBox);
875        }
876        if (_car != null) {
877            trainBox.setSelectedItem(_car.getTrain());
878        }
879    }
880
881    private Car getTestCar(Car car, String loadName) {
882        Car c = car;
883        // clone car and set the load and a length of zero
884        if (car != null) {
885            c = car.copy();
886            c.setLoadName(loadName);
887            c.setLength(Integer.toString(-RollingStock.COUPLERS)); // ignore car length
888        }
889        return c;
890    }
891
892    @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "GUI ease of use")
893    public void setDestinationEnabled(boolean enable) {
894        enableDestination = !enableDestination;
895        enableDestinationFields(!locationUnknownCheckBox.isSelected());
896    }
897
898    @Override
899    public void dispose() {
900        if (lef != null) {
901            lef.dispose();
902        }
903        if (cef != null) {
904            cef.dispose();
905        }
906        if (def != null) {
907            def.dispose();
908        }
909        if (tctf != null) {
910            tctf.dispose();
911        }
912        InstanceManager.getDefault(CarLoads.class).removePropertyChangeListener(this);
913        InstanceManager.getDefault(KernelManager.class).removePropertyChangeListener(this);
914        InstanceManager.getDefault(DivisionManager.class).removePropertyChangeListener(this);
915        carManager.removePropertyChangeListener(this);
916        super.dispose();
917    }
918
919    @Override
920    public void propertyChange(java.beans.PropertyChangeEvent e) {
921        log.debug("PropertyChange ({}) new ({})", e.getPropertyName(), e.getNewValue());
922        super.propertyChange(e);
923        if (e.getPropertyName().equals(Car.FINAL_DESTINATION_CHANGED_PROPERTY) ||
924                e.getPropertyName().equals(Car.FINAL_DESTINATION_TRACK_CHANGED_PROPERTY)) {
925            updateFinalDestinationComboBoxes();
926        }
927        if (e.getPropertyName().equals(CarLoads.LOAD_CHANGED_PROPERTY) ||
928                e.getPropertyName().equals(Car.LOAD_CHANGED_PROPERTY)) {
929            updateLoadComboBox();
930        }
931        if (e.getPropertyName().equals(CarLoads.LOAD_CHANGED_PROPERTY) ||
932                e.getPropertyName().equals(CarLoads.LOAD_TYPE_CHANGED_PROPERTY) ||
933                e.getPropertyName().equals(Car.RWE_LOAD_CHANGED_PROPERTY)) {
934            updateRweLoadComboBox();
935        }
936        if (e.getPropertyName().equals(CarLoads.LOAD_CHANGED_PROPERTY) ||
937                e.getPropertyName().equals(CarLoads.LOAD_TYPE_CHANGED_PROPERTY) ||
938                e.getPropertyName().equals(Car.RWL_LOAD_CHANGED_PROPERTY)) {
939            updateRwlLoadComboBox();
940        }
941        if (e.getPropertyName().equals(Car.RETURN_WHEN_EMPTY_CHANGED_PROPERTY)) {
942            updateReturnWhenEmptyComboBoxes();
943        }
944        if (e.getPropertyName().equals(Car.RETURN_WHEN_LOADED_CHANGED_PROPERTY)) {
945            updateReturnWhenLoadedComboBoxes();
946        }
947        if (e.getPropertyName().equals(KernelManager.LISTLENGTH_CHANGED_PROPERTY) ||
948                e.getPropertyName().equals(Car.KERNEL_NAME_CHANGED_PROPERTY)) {
949            updateKernelComboBox();
950        }
951        if (e.getPropertyName().equals(DivisionManager.LISTLENGTH_CHANGED_PROPERTY)) {
952            updateDivisionComboBox();
953        }
954        if (e.getPropertyName().equals(RollingStock.TRAIN_CHANGED_PROPERTY)) {
955            enableDestinationFields(!locationUnknownCheckBox.isSelected());
956        }
957        if (e.getPropertyName().equals(CarAttributeEditFrame.DISPOSE)) {
958            cef = null;
959        }
960    }
961
962    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CarSetFrame.class);
963}