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                // ignore custom load warning
460                if (!status.equals(Track.OKAY) && !status.contains(Track.CUSTOM)) {
461                    JmriJOptionPane.showMessageDialog(this,
462                            Bundle.getMessage("rsCanNotFinalMsg", car.toString(), status),
463                            Bundle.getMessage("rsCanNotFinal"), JmriJOptionPane.WARNING_MESSAGE);
464                    return false;
465                } else {
466                    // check to see if car can be routed to final destination
467                    Router router = InstanceManager.getDefault(Router.class);
468                    if (!router.isCarRouteable(car, null, car.getFinalDestination(), finalDestTrack, null)) {
469                        JmriJOptionPane.showMessageDialog(this,
470                                Bundle.getMessage("rsCanNotRouteMsg", car.toString(),
471                                        car.getLocationName(), car.getTrackName(), car.getFinalDestinationName(),
472                                        car.getFinalDestinationTrackName()),
473                                Bundle.getMessage("rsCanNotFinal"), JmriJOptionPane.WARNING_MESSAGE);
474                        return false;
475                    }
476                }
477            }
478        }
479        return true;
480    }
481    
482    private void setCarKernel(Car car) {
483        if (!ignoreKernelCheckBox.isSelected() && kernelComboBox.getSelectedItem() != null) {
484            if (kernelComboBox.getSelectedItem().equals(RollingStockManager.NONE)) {
485                car.setKernel(null);
486                if (!car.isPassenger()) {
487                    car.setBlocking(Car.DEFAULT_BLOCKING_ORDER);
488                }
489            } else if (!car.getKernelName().equals(kernelComboBox.getSelectedItem())) {
490                car.setKernel(InstanceManager.getDefault(KernelManager.class).getKernelByName((String) kernelComboBox.getSelectedItem()));
491                // if car has FRED or is caboose make lead
492                if (car.hasFred() || car.isCaboose()) {
493                    car.getKernel().setLead(car);
494                }
495                car.setBlocking(car.getKernel().getSize());
496            }
497        }
498    }
499    
500    private boolean setCarRWE(Car car) {
501        if (!ignoreRWECheckBox.isSelected()) {
502            // check that RWE load is valid for this car's type
503            if (carLoads.getNames(car.getTypeName()).contains(loadReturnWhenEmptyBox.getSelectedItem())) {
504                car.setReturnWhenEmptyLoadName((String) loadReturnWhenEmptyBox.getSelectedItem());
505            } else {
506                log.debug("Car ({}) type ({}) doesn't support RWE load ({})", car, car.getTypeName(),
507                        loadReturnWhenEmptyBox.getSelectedItem());
508                JmriJOptionPane.showMessageDialog(this,
509                        Bundle.getMessage("carLoadNotValid",
510                                loadReturnWhenEmptyBox.getSelectedItem(), car.getTypeName()),
511                        Bundle.getMessage("carCanNotChangeRweLoad"), JmriJOptionPane.WARNING_MESSAGE);
512            }
513            if (destReturnWhenEmptyBox.getSelectedItem() == null) {
514                car.setReturnWhenEmptyDestination(null);
515                car.setReturnWhenEmptyDestTrack(null);
516            } else {
517                Location locationRWE = (Location) destReturnWhenEmptyBox.getSelectedItem();
518                if (trackReturnWhenEmptyBox.getSelectedItem() != null) {
519                    Track trackRWE = (Track) trackReturnWhenEmptyBox.getSelectedItem();
520                    // warn user if they selected a staging track
521                    if (trackRWE != null && trackRWE.isStaging()) {
522                        log.debug("Return when empty track ({}) is staging", trackRWE.getName());
523                        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("rsDoNotSelectStaging"),
524                                Bundle.getMessage("carCanNotRWE"), JmriJOptionPane.ERROR_MESSAGE);
525                        return false;
526                    }
527                    // use a test car with a load of "RWE" and no length
528                    String status = getTestCar(car, car.getReturnWhenEmptyLoadName()).checkDestination(locationRWE,
529                            trackRWE);
530                    if (!status.equals(Track.OKAY)) {
531                        JmriJOptionPane.showMessageDialog(this,
532                                Bundle.getMessage("carCanNotRWEMsg", car.toString(), locationRWE,
533                                        trackRWE, status),
534                                Bundle.getMessage("carCanNotRWE"), JmriJOptionPane.WARNING_MESSAGE);
535                    }
536                    car.setReturnWhenEmptyDestTrack(trackRWE);
537                } else {
538                    car.setReturnWhenEmptyDestTrack(null);
539                }
540                car.setReturnWhenEmptyDestination(locationRWE);
541            }
542        }
543        return true;
544    }
545    
546    private boolean setCarRWL(Car car) {
547        if (!ignoreRWLCheckBox.isSelected()) {
548            // check that RWL load is valid for this car's type
549            if (carLoads.getNames(car.getTypeName()).contains(loadReturnWhenLoadedBox.getSelectedItem())) {
550                car.setReturnWhenLoadedLoadName((String) loadReturnWhenLoadedBox.getSelectedItem());
551            } else {
552                log.debug("Car ({}) type ({}) doesn't support RWL load ({})", car, car.getTypeName(),
553                        loadReturnWhenLoadedBox.getSelectedItem());
554                JmriJOptionPane.showMessageDialog(this,
555                        Bundle.getMessage("carLoadNotValid",
556                                loadReturnWhenEmptyBox.getSelectedItem(), car.getTypeName()),
557                        Bundle.getMessage("carCanNotChangeRwlLoad"), JmriJOptionPane.WARNING_MESSAGE);
558            }
559            if (destReturnWhenLoadedBox.getSelectedItem() == null) {
560                car.setReturnWhenLoadedDestination(null);
561                car.setReturnWhenLoadedDestTrack(null);
562            } else {
563                Location locationRWL = (Location) destReturnWhenLoadedBox.getSelectedItem();
564                if (trackReturnWhenLoadedBox.getSelectedItem() != null) {
565                    Track trackRWL = (Track) trackReturnWhenLoadedBox.getSelectedItem();
566                    // warn user if they selected a staging track
567                    if (trackRWL != null && trackRWL.isStaging()) {
568                        log.debug("Return when loaded track ({}) is staging", trackRWL.getName());
569                        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("rsDoNotSelectStaging"),
570                                Bundle.getMessage("carCanNotRWL"), JmriJOptionPane.ERROR_MESSAGE);
571                        return false;
572                    }
573                    // use a test car with a load of "RWL" and no length
574                    String status = getTestCar(car, car.getReturnWhenLoadedLoadName()).checkDestination(locationRWL,
575                            trackRWL);
576                    if (!status.equals(Track.OKAY)) {
577                        JmriJOptionPane.showMessageDialog(this,
578                                Bundle.getMessage("carCanNotRWLMsg", car.toString(), locationRWL,
579                                        trackRWL, status),
580                                Bundle.getMessage("carCanNotRWL"), JmriJOptionPane.WARNING_MESSAGE);
581                    }
582                    car.setReturnWhenLoadedDestTrack(trackRWL);
583                } else {
584                    car.setReturnWhenLoadedDestTrack(null);
585                }
586                car.setReturnWhenLoadedDestination(locationRWL);
587            }
588        }
589        return true;
590    }
591    
592    private boolean applySchedule(Car car, Track saveTrack) {
593        if (!ignoreLocationCheckBox.isSelected() &&
594                trackLocationBox.getSelectedItem() != null &&
595                saveTrack != trackLocationBox.getSelectedItem()) {
596            Track track = (Track) trackLocationBox.getSelectedItem();
597            if (track.getSchedule() != null) {
598                if (JmriJOptionPane
599                        .showConfirmDialog(this,
600                                Bundle.getMessage("rsDoYouWantSchedule", car.toString()),
601                                Bundle.getMessage("rsSpurHasSchedule", track.getName(),
602                                        track.getScheduleName()),
603                                JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) {
604                    String results = track.checkSchedule(car);
605                    if (!results.equals(Track.OKAY)) {
606                        JmriJOptionPane.showMessageDialog(this,
607                                Bundle.getMessage("rsNotAbleToApplySchedule", results),
608                                Bundle.getMessage("rsApplyingScheduleFailed"), JmriJOptionPane.ERROR_MESSAGE);
609                        // restore previous location and track so we'll ask to test schedule again
610                        if (saveTrack != null) {
611                            car.setLocation(saveTrack.getLocation(), saveTrack);
612                        } else {
613                            car.setLocation(null, null);
614                        }
615                        return false;
616                    }
617                    // now apply schedule to car
618                    track.scheduleNext(car);
619                    car.loadNext(track);
620                }
621            }
622        }
623        return true;
624    }
625    
626    private boolean checkTrainLoad(Car car) {
627        if (car.getTrain() != null) {
628            Train train = car.getTrain();
629            if (!train.isLoadNameAccepted(car.getLoadName(), car.getTypeName())) {
630                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("carTrainNotServLoad",
631                        car.getLoadName(), train.getName()), Bundle.getMessage("rsNotMove"), JmriJOptionPane.ERROR_MESSAGE);
632                // prevent rs from being picked up and delivered
633                setRouteLocationAndDestination(car, train, null, null);
634                return false;
635            }
636        }
637        return true;
638    }
639
640    TrainByCarTypeFrame tctf = null;
641    
642    private boolean checkTrainRoute(Car car) {
643        if (car.getTrain() != null) {
644            Train train = car.getTrain();
645            if (car.getLocation() != null && car.getDestination() != null && !train.isServiceable(car)) {
646                JmriJOptionPane.showMessageDialog(this,
647                        Bundle.getMessage("carTrainNotService", car.toString(), train.getName()),
648                        Bundle.getMessage("rsNotMove"), JmriJOptionPane.ERROR_MESSAGE);
649                // show the train's route and car location
650                if (tctf != null) {
651                    tctf.dispose();
652                }
653                tctf = new TrainByCarTypeFrame(car);
654                // prevent rs from being picked up and delivered
655                setRouteLocationAndDestination(car, train, null, null);
656                return false;
657            }
658        }
659        return true;
660    }
661
662    /**
663     * Update locations if load changes. New load could change which track are
664     * allowed if auto selected.
665     */
666    protected void updateComboBoxesLoadChange() {
667        if (autoTrackCheckBox.isSelected()) {
668            updateLocationTrackComboBox();
669        }
670        if (autoDestinationTrackCheckBox.isSelected()) {
671            updateDestinationTrackComboBox();
672        }
673        if (autoFinalDestTrackCheckBox.isSelected()) {
674            updateFinalDestinationTrack();
675        }
676    }
677
678    @Override
679    protected boolean updateGroup(List<Car> list) {
680        for (Car car : list) {
681            if (car == _car) {
682                continue;
683            }
684            // make all cars in kernel the same
685            if (!ignoreRWECheckBox.isSelected()) {
686                car.setReturnWhenEmptyDestination(_car.getReturnWhenEmptyDestination());
687                car.setReturnWhenEmptyDestTrack(_car.getReturnWhenEmptyDestTrack());
688            }
689            if (!ignoreRWLCheckBox.isSelected()) {
690                car.setReturnWhenLoadedDestination(_car.getReturnWhenLoadedDestination());
691                car.setReturnWhenLoadedDestTrack(_car.getReturnWhenLoadedDestTrack());
692            }
693            if (!ignoreFinalDestinationCheckBox.isSelected()) {
694                car.setFinalDestination(_car.getFinalDestination());
695                car.setFinalDestinationTrack(_car.getFinalDestinationTrack());
696            }
697            // update car load
698            if (!ignoreLoadCheckBox.isSelected() && carLoads.containsName(car.getTypeName(), _car.getLoadName())) {
699                car.setLoadName(_car.getLoadName());
700                car.setWait(0); // car could be at spur with schedule
701                car.setScheduleItemId(Car.NONE);
702            }
703            // update kernel
704            if (!ignoreKernelCheckBox.isSelected()) {
705                car.setKernel(_car.getKernel());
706            }
707            // update division
708            if (!ignoreDivisionCheckBox.isSelected()) {
709                car.setDivision(_car.getDivision());
710            }
711        }
712        return super.updateGroup(list);
713    }
714
715    @Override
716    public void checkBoxActionPerformed(java.awt.event.ActionEvent ae) {
717        super.checkBoxActionPerformed(ae);
718        if (ae.getSource() == autoFinalDestTrackCheckBox) {
719            updateFinalDestinationTrack();
720        }
721        if (ae.getSource() == autoReturnWhenEmptyTrackCheckBox) {
722            updateReturnWhenEmptyTrack();
723        }
724        if (ae.getSource() == autoReturnWhenLoadedTrackCheckBox) {
725            updateReturnWhenLoadedTrack();
726        }
727        if (ae.getSource() == autoTrainCheckBox) {
728            updateTrainComboBox();
729        }
730        if (ae.getSource() == ignoreRWECheckBox) {
731            destReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected());
732            trackReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected());
733            loadReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected());
734            autoReturnWhenEmptyTrackCheckBox.setEnabled(!ignoreRWECheckBox.isSelected());
735        }
736        if (ae.getSource() == ignoreRWLCheckBox) {
737            destReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected());
738            trackReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected());
739            loadReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected());
740            autoReturnWhenLoadedTrackCheckBox.setEnabled(!ignoreRWLCheckBox.isSelected());
741        }
742        if (ae.getSource() == ignoreLoadCheckBox) {
743            loadComboBox.setEnabled(!ignoreLoadCheckBox.isSelected());
744            editLoadButton.setEnabled(!ignoreLoadCheckBox.isSelected() && _car != null);
745        }
746        if (ae.getSource() == ignoreDivisionCheckBox) {
747            divisionComboBox.setEnabled(!ignoreDivisionCheckBox.isSelected());
748            editDivisionButton.setEnabled(!ignoreDivisionCheckBox.isSelected());
749        }
750        if (ae.getSource() == ignoreKernelCheckBox) {
751            kernelComboBox.setEnabled(!ignoreKernelCheckBox.isSelected());
752            editKernelButton.setEnabled(!ignoreKernelCheckBox.isSelected());
753        }
754    }
755
756    protected void updateReturnWhenEmptyComboBoxes() {
757        if (_car != null) {
758            log.debug("Updating return when empty for car ({})", _car);
759            destReturnWhenEmptyBox.setSelectedItem(_car.getReturnWhenEmptyDestination());
760        }
761        updateReturnWhenEmptyTrack();
762    }
763
764    protected void updateReturnWhenEmptyTrack() {
765        if (destReturnWhenEmptyBox.getSelectedItem() == null) {
766            trackReturnWhenEmptyBox.removeAllItems();
767        } else {
768            log.debug("CarSetFrame sees return when empty: {}", destReturnWhenEmptyBox.getSelectedItem());
769            Location loc = (Location) destReturnWhenEmptyBox.getSelectedItem();
770            loc.updateComboBox(trackReturnWhenEmptyBox, getTestCar(_car, _car.getReturnWhenEmptyLoadName()),
771                    autoReturnWhenEmptyTrackCheckBox.isSelected(), true);
772            if (_car != null &&
773                    _car.getReturnWhenEmptyDestination() != null &&
774                    _car.getReturnWhenEmptyDestination().equals(loc) &&
775                    _car.getReturnWhenEmptyDestTrack() != null) {
776                trackReturnWhenEmptyBox.setSelectedItem(_car.getReturnWhenEmptyDestTrack());
777            }
778        }
779    }
780
781    protected void updateReturnWhenLoadedComboBoxes() {
782        if (_car != null) {
783            log.debug("Updating return when loaded for car ({})", _car);
784            destReturnWhenLoadedBox.setSelectedItem(_car.getReturnWhenLoadedDestination());
785        }
786        updateReturnWhenLoadedTrack();
787    }
788
789    protected void updateReturnWhenLoadedTrack() {
790        if (destReturnWhenLoadedBox.getSelectedItem() == null) {
791            trackReturnWhenLoadedBox.removeAllItems();
792        } else {
793            log.debug("CarSetFrame sees return when empty: {}", destReturnWhenLoadedBox.getSelectedItem());
794            Location loc = (Location) destReturnWhenLoadedBox.getSelectedItem();
795            loc.updateComboBox(trackReturnWhenLoadedBox, getTestCar(_car, _car.getReturnWhenLoadedLoadName()),
796                    autoReturnWhenLoadedTrackCheckBox.isSelected(), true);
797            if (_car != null &&
798                    _car.getReturnWhenLoadedDestination() != null &&
799                    _car.getReturnWhenLoadedDestination().equals(loc) &&
800                    _car.getReturnWhenLoadedDestTrack() != null) {
801                trackReturnWhenLoadedBox.setSelectedItem(_car.getReturnWhenLoadedDestTrack());
802            }
803        }
804    }
805
806    protected void updateFinalDestinationComboBoxes() {
807        if (_car != null) {
808            log.debug("Updating final destinations for car ({})", _car);
809            finalDestinationBox.setSelectedItem(_car.getFinalDestination());
810        }
811        updateFinalDestinationTrack();
812    }
813
814    protected void updateFinalDestinationTrack() {
815        if (finalDestinationBox.getSelectedItem() == null) {
816            finalDestTrackBox.removeAllItems();
817        } else {
818            log.debug("CarSetFrame sees final destination: {}", finalDestinationBox.getSelectedItem());
819            Location l = (Location) finalDestinationBox.getSelectedItem();
820            l.updateComboBox(finalDestTrackBox, _car, autoFinalDestTrackCheckBox.isSelected(), true);
821            if (_car != null &&
822                    _car.getFinalDestination() != null &&
823                    _car.getFinalDestination().equals(l) &&
824                    _car.getFinalDestinationTrack() != null) {
825                finalDestTrackBox.setSelectedItem(_car.getFinalDestinationTrack());
826            }
827        }
828    }
829
830    protected void updateLoadComboBox() {
831        if (_car != null) {
832            log.debug("Updating load box for car ({})", _car);
833            carLoads.updateComboBox(_car.getTypeName(), loadComboBox);
834            loadComboBox.setSelectedItem(_car.getLoadName());
835        }
836    }
837
838    protected void updateRweLoadComboBox() {
839        if (_car != null) {
840            log.debug("Updating RWE load box for car ({})", _car);
841            carLoads.updateRweComboBox(_car.getTypeName(), loadReturnWhenEmptyBox);
842            loadReturnWhenEmptyBox.setSelectedItem(_car.getReturnWhenEmptyLoadName());
843        }
844    }
845
846    protected void updateRwlLoadComboBox() {
847        if (_car != null) {
848            log.debug("Updating RWL load box for car ({})", _car);
849            carLoads.updateRwlComboBox(_car.getTypeName(), loadReturnWhenLoadedBox);
850            loadReturnWhenLoadedBox.setSelectedItem(_car.getReturnWhenLoadedLoadName());
851        }
852    }
853
854    protected void updateKernelComboBox() {
855        InstanceManager.getDefault(KernelManager.class).updateComboBox(kernelComboBox);
856        if (_car != null) {
857            kernelComboBox.setSelectedItem(_car.getKernelName());
858        }
859    }
860    
861    protected void updateDivisionComboBox() {
862        InstanceManager.getDefault(DivisionManager.class).updateComboBox(divisionComboBox);
863        if (_car != null) {
864            divisionComboBox.setSelectedItem(_car.getDivision());
865        }
866    }
867
868    @Override
869    protected void updateTrainComboBox() {
870        log.debug("update train combo box");
871        if (_car != null && autoTrainCheckBox.isSelected()) {
872            log.debug("Updating train box for car ({})", _car);
873            trainManager.updateTrainComboBox(trainBox, _car);
874        } else {
875            trainManager.updateTrainComboBox(trainBox);
876        }
877        if (_car != null) {
878            trainBox.setSelectedItem(_car.getTrain());
879        }
880    }
881
882    private Car getTestCar(Car car, String loadName) {
883        Car c = car;
884        // clone car and set the load and a length of zero
885        if (car != null) {
886            c = car.copy();
887            c.setLoadName(loadName);
888            c.setLength(Integer.toString(-RollingStock.COUPLERS)); // ignore car length
889        }
890        return c;
891    }
892
893    @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "GUI ease of use")
894    public void setDestinationEnabled(boolean enable) {
895        enableDestination = !enableDestination;
896        enableDestinationFields(!locationUnknownCheckBox.isSelected());
897    }
898
899    @Override
900    public void dispose() {
901        if (lef != null) {
902            lef.dispose();
903        }
904        if (cef != null) {
905            cef.dispose();
906        }
907        if (def != null) {
908            def.dispose();
909        }
910        if (tctf != null) {
911            tctf.dispose();
912        }
913        InstanceManager.getDefault(CarLoads.class).removePropertyChangeListener(this);
914        InstanceManager.getDefault(KernelManager.class).removePropertyChangeListener(this);
915        InstanceManager.getDefault(DivisionManager.class).removePropertyChangeListener(this);
916        carManager.removePropertyChangeListener(this);
917        super.dispose();
918    }
919
920    @Override
921    public void propertyChange(java.beans.PropertyChangeEvent e) {
922        log.debug("PropertyChange ({}) new ({})", e.getPropertyName(), e.getNewValue());
923        super.propertyChange(e);
924        if (e.getPropertyName().equals(Car.FINAL_DESTINATION_CHANGED_PROPERTY) ||
925                e.getPropertyName().equals(Car.FINAL_DESTINATION_TRACK_CHANGED_PROPERTY)) {
926            updateFinalDestinationComboBoxes();
927        }
928        if (e.getPropertyName().equals(CarLoads.LOAD_CHANGED_PROPERTY) ||
929                e.getPropertyName().equals(Car.LOAD_CHANGED_PROPERTY)) {
930            updateLoadComboBox();
931        }
932        if (e.getPropertyName().equals(CarLoads.LOAD_CHANGED_PROPERTY) ||
933                e.getPropertyName().equals(CarLoads.LOAD_TYPE_CHANGED_PROPERTY) ||
934                e.getPropertyName().equals(Car.RWE_LOAD_CHANGED_PROPERTY)) {
935            updateRweLoadComboBox();
936        }
937        if (e.getPropertyName().equals(CarLoads.LOAD_CHANGED_PROPERTY) ||
938                e.getPropertyName().equals(CarLoads.LOAD_TYPE_CHANGED_PROPERTY) ||
939                e.getPropertyName().equals(Car.RWL_LOAD_CHANGED_PROPERTY)) {
940            updateRwlLoadComboBox();
941        }
942        if (e.getPropertyName().equals(Car.RETURN_WHEN_EMPTY_CHANGED_PROPERTY)) {
943            updateReturnWhenEmptyComboBoxes();
944        }
945        if (e.getPropertyName().equals(Car.RETURN_WHEN_LOADED_CHANGED_PROPERTY)) {
946            updateReturnWhenLoadedComboBoxes();
947        }
948        if (e.getPropertyName().equals(KernelManager.LISTLENGTH_CHANGED_PROPERTY) ||
949                e.getPropertyName().equals(Car.KERNEL_NAME_CHANGED_PROPERTY)) {
950            updateKernelComboBox();
951        }
952        if (e.getPropertyName().equals(DivisionManager.LISTLENGTH_CHANGED_PROPERTY)) {
953            updateDivisionComboBox();
954        }
955        if (e.getPropertyName().equals(RollingStock.TRAIN_CHANGED_PROPERTY)) {
956            enableDestinationFields(!locationUnknownCheckBox.isSelected());
957        }
958        if (e.getPropertyName().equals(CarAttributeEditFrame.DISPOSE)) {
959            cef = null;
960        }
961    }
962
963    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CarSetFrame.class);
964}