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    protected JComboBox<String> loadComboBox = carLoads.getComboBox(null);
053    protected JComboBox<String> kernelComboBox = InstanceManager.getDefault(KernelManager.class).getComboBox();
054
055    // buttons
056    protected JButton editDivisionButton = new JButton(Bundle.getMessage("ButtonEdit"));
057    protected JButton editLoadButton = new JButton(Bundle.getMessage("ButtonEdit"));
058    protected 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                car.setRoutePath(_car.getRoutePath());
697            }
698            // update car load
699            if (!ignoreLoadCheckBox.isSelected() && carLoads.containsName(car.getTypeName(), _car.getLoadName())) {
700                car.setLoadName(_car.getLoadName());
701                car.setWait(0); // car could be at spur with schedule
702                car.setScheduleItemId(Car.NONE);
703            }
704            // update kernel
705            if (!ignoreKernelCheckBox.isSelected()) {
706                car.setKernel(_car.getKernel());
707            }
708            // update division
709            if (!ignoreDivisionCheckBox.isSelected()) {
710                car.setDivision(_car.getDivision());
711            }
712        }
713        return super.updateGroup(list);
714    }
715
716    @Override
717    public void checkBoxActionPerformed(java.awt.event.ActionEvent ae) {
718        super.checkBoxActionPerformed(ae);
719        if (ae.getSource() == autoFinalDestTrackCheckBox) {
720            updateFinalDestinationTrack();
721        }
722        if (ae.getSource() == autoReturnWhenEmptyTrackCheckBox) {
723            updateReturnWhenEmptyTrack();
724        }
725        if (ae.getSource() == autoReturnWhenLoadedTrackCheckBox) {
726            updateReturnWhenLoadedTrack();
727        }
728        if (ae.getSource() == autoTrainCheckBox) {
729            updateTrainComboBox();
730        }
731        if (ae.getSource() == ignoreRWECheckBox) {
732            destReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected());
733            trackReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected());
734            loadReturnWhenEmptyBox.setEnabled(!ignoreRWECheckBox.isSelected());
735            autoReturnWhenEmptyTrackCheckBox.setEnabled(!ignoreRWECheckBox.isSelected());
736        }
737        if (ae.getSource() == ignoreRWLCheckBox) {
738            destReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected());
739            trackReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected());
740            loadReturnWhenLoadedBox.setEnabled(!ignoreRWLCheckBox.isSelected());
741            autoReturnWhenLoadedTrackCheckBox.setEnabled(!ignoreRWLCheckBox.isSelected());
742        }
743        if (ae.getSource() == ignoreLoadCheckBox) {
744            loadComboBox.setEnabled(!ignoreLoadCheckBox.isSelected());
745            editLoadButton.setEnabled(!ignoreLoadCheckBox.isSelected() && _car != null);
746        }
747        if (ae.getSource() == ignoreDivisionCheckBox) {
748            divisionComboBox.setEnabled(!ignoreDivisionCheckBox.isSelected());
749            editDivisionButton.setEnabled(!ignoreDivisionCheckBox.isSelected());
750        }
751        if (ae.getSource() == ignoreKernelCheckBox) {
752            kernelComboBox.setEnabled(!ignoreKernelCheckBox.isSelected());
753            editKernelButton.setEnabled(!ignoreKernelCheckBox.isSelected());
754        }
755    }
756
757    protected void updateReturnWhenEmptyComboBoxes() {
758        if (_car != null) {
759            log.debug("Updating return when empty for car ({})", _car);
760            destReturnWhenEmptyBox.setSelectedItem(_car.getReturnWhenEmptyDestination());
761        }
762        updateReturnWhenEmptyTrack();
763    }
764
765    protected void updateReturnWhenEmptyTrack() {
766        if (destReturnWhenEmptyBox.getSelectedItem() == null) {
767            trackReturnWhenEmptyBox.removeAllItems();
768        } else {
769            log.debug("CarSetFrame sees return when empty: {}", destReturnWhenEmptyBox.getSelectedItem());
770            Location loc = (Location) destReturnWhenEmptyBox.getSelectedItem();
771            loc.updateComboBox(trackReturnWhenEmptyBox, getTestCar(_car, _car.getReturnWhenEmptyLoadName()),
772                    autoReturnWhenEmptyTrackCheckBox.isSelected(), true);
773            if (_car != null &&
774                    _car.getReturnWhenEmptyDestination() != null &&
775                    _car.getReturnWhenEmptyDestination().equals(loc) &&
776                    _car.getReturnWhenEmptyDestTrack() != null) {
777                trackReturnWhenEmptyBox.setSelectedItem(_car.getReturnWhenEmptyDestTrack());
778            }
779        }
780    }
781
782    protected void updateReturnWhenLoadedComboBoxes() {
783        if (_car != null) {
784            log.debug("Updating return when loaded for car ({})", _car);
785            destReturnWhenLoadedBox.setSelectedItem(_car.getReturnWhenLoadedDestination());
786        }
787        updateReturnWhenLoadedTrack();
788    }
789
790    protected void updateReturnWhenLoadedTrack() {
791        if (destReturnWhenLoadedBox.getSelectedItem() == null) {
792            trackReturnWhenLoadedBox.removeAllItems();
793        } else {
794            log.debug("CarSetFrame sees return when empty: {}", destReturnWhenLoadedBox.getSelectedItem());
795            Location loc = (Location) destReturnWhenLoadedBox.getSelectedItem();
796            loc.updateComboBox(trackReturnWhenLoadedBox, getTestCar(_car, _car.getReturnWhenLoadedLoadName()),
797                    autoReturnWhenLoadedTrackCheckBox.isSelected(), true);
798            if (_car != null &&
799                    _car.getReturnWhenLoadedDestination() != null &&
800                    _car.getReturnWhenLoadedDestination().equals(loc) &&
801                    _car.getReturnWhenLoadedDestTrack() != null) {
802                trackReturnWhenLoadedBox.setSelectedItem(_car.getReturnWhenLoadedDestTrack());
803            }
804        }
805    }
806
807    protected void updateFinalDestinationComboBoxes() {
808        if (_car != null) {
809            log.debug("Updating final destinations for car ({})", _car);
810            finalDestinationBox.setSelectedItem(_car.getFinalDestination());
811        }
812        updateFinalDestinationTrack();
813    }
814
815    protected void updateFinalDestinationTrack() {
816        if (finalDestinationBox.getSelectedItem() == null) {
817            finalDestTrackBox.removeAllItems();
818        } else {
819            log.debug("CarSetFrame sees final destination: {}", finalDestinationBox.getSelectedItem());
820            Location l = (Location) finalDestinationBox.getSelectedItem();
821            l.updateComboBox(finalDestTrackBox, _car, autoFinalDestTrackCheckBox.isSelected(), true);
822            if (_car != null &&
823                    _car.getFinalDestination() != null &&
824                    _car.getFinalDestination().equals(l) &&
825                    _car.getFinalDestinationTrack() != null) {
826                finalDestTrackBox.setSelectedItem(_car.getFinalDestinationTrack());
827            }
828        }
829    }
830
831    protected void updateLoadComboBox() {
832        if (_car != null) {
833            log.debug("Updating load box for car ({})", _car);
834            carLoads.updateComboBox(_car.getTypeName(), loadComboBox);
835            loadComboBox.setSelectedItem(_car.getLoadName());
836        }
837    }
838
839    protected void updateRweLoadComboBox() {
840        if (_car != null) {
841            log.debug("Updating RWE load box for car ({})", _car);
842            carLoads.updateRweComboBox(_car.getTypeName(), loadReturnWhenEmptyBox);
843            loadReturnWhenEmptyBox.setSelectedItem(_car.getReturnWhenEmptyLoadName());
844        }
845    }
846
847    protected void updateRwlLoadComboBox() {
848        if (_car != null) {
849            log.debug("Updating RWL load box for car ({})", _car);
850            carLoads.updateRwlComboBox(_car.getTypeName(), loadReturnWhenLoadedBox);
851            loadReturnWhenLoadedBox.setSelectedItem(_car.getReturnWhenLoadedLoadName());
852        }
853    }
854
855    protected void updateKernelComboBox() {
856        InstanceManager.getDefault(KernelManager.class).updateComboBox(kernelComboBox);
857        if (_car != null) {
858            kernelComboBox.setSelectedItem(_car.getKernelName());
859        }
860    }
861    
862    protected void updateDivisionComboBox() {
863        InstanceManager.getDefault(DivisionManager.class).updateComboBox(divisionComboBox);
864        if (_car != null) {
865            divisionComboBox.setSelectedItem(_car.getDivision());
866        }
867    }
868
869    @Override
870    protected void updateTrainComboBox() {
871        log.debug("update train combo box");
872        if (_car != null && autoTrainCheckBox.isSelected()) {
873            log.debug("Updating train box for car ({})", _car);
874            trainManager.updateTrainComboBox(trainBox, _car);
875        } else {
876            trainManager.updateTrainComboBox(trainBox);
877        }
878        if (_car != null) {
879            trainBox.setSelectedItem(_car.getTrain());
880        }
881    }
882
883    private Car getTestCar(Car car, String loadName) {
884        Car c = car;
885        // clone car and set the load and a length of zero
886        if (car != null) {
887            c = car.copy();
888            c.setLoadName(loadName);
889            c.setLength(Integer.toString(-RollingStock.COUPLERS)); // ignore car length
890        }
891        return c;
892    }
893
894    @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "GUI ease of use")
895    public void setDestinationEnabled(boolean enable) {
896        enableDestination = !enableDestination;
897        enableDestinationFields(!locationUnknownCheckBox.isSelected());
898    }
899
900    @Override
901    public void dispose() {
902        if (lef != null) {
903            lef.dispose();
904        }
905        if (cef != null) {
906            cef.removePropertyChangeListener(this);
907            cef.dispose();
908        }
909        if (def != null) {
910            def.dispose();
911        }
912        if (tctf != null) {
913            tctf.dispose();
914        }
915        InstanceManager.getDefault(CarLoads.class).removePropertyChangeListener(this);
916        InstanceManager.getDefault(KernelManager.class).removePropertyChangeListener(this);
917        InstanceManager.getDefault(DivisionManager.class).removePropertyChangeListener(this);
918        carManager.removePropertyChangeListener(this);
919        super.dispose();
920    }
921
922    @Override
923    public void propertyChange(java.beans.PropertyChangeEvent e) {
924        log.debug("PropertyChange ({}) new ({})", e.getPropertyName(), e.getNewValue());
925        super.propertyChange(e);
926        if (e.getPropertyName().equals(Car.FINAL_DESTINATION_CHANGED_PROPERTY) ||
927                e.getPropertyName().equals(Car.FINAL_DESTINATION_TRACK_CHANGED_PROPERTY)) {
928            updateFinalDestinationComboBoxes();
929        }
930        if (e.getPropertyName().equals(CarLoads.LOAD_CHANGED_PROPERTY) ||
931                e.getPropertyName().equals(Car.LOAD_CHANGED_PROPERTY)) {
932            updateLoadComboBox();
933        }
934        if (e.getPropertyName().equals(CarLoads.LOAD_CHANGED_PROPERTY) ||
935                e.getPropertyName().equals(CarLoads.LOAD_TYPE_CHANGED_PROPERTY) ||
936                e.getPropertyName().equals(Car.RWE_LOAD_CHANGED_PROPERTY)) {
937            updateRweLoadComboBox();
938        }
939        if (e.getPropertyName().equals(CarLoads.LOAD_CHANGED_PROPERTY) ||
940                e.getPropertyName().equals(CarLoads.LOAD_TYPE_CHANGED_PROPERTY) ||
941                e.getPropertyName().equals(Car.RWL_LOAD_CHANGED_PROPERTY)) {
942            updateRwlLoadComboBox();
943        }
944        if (e.getPropertyName().equals(Car.RETURN_WHEN_EMPTY_CHANGED_PROPERTY)) {
945            updateReturnWhenEmptyComboBoxes();
946        }
947        if (e.getPropertyName().equals(Car.RETURN_WHEN_LOADED_CHANGED_PROPERTY)) {
948            updateReturnWhenLoadedComboBoxes();
949        }
950        if (e.getPropertyName().equals(KernelManager.LISTLENGTH_CHANGED_PROPERTY) ||
951                e.getPropertyName().equals(Car.KERNEL_NAME_CHANGED_PROPERTY)) {
952            updateKernelComboBox();
953        }
954        if (e.getPropertyName().equals(DivisionManager.LISTLENGTH_CHANGED_PROPERTY)) {
955            updateDivisionComboBox();
956        }
957        if (e.getPropertyName().equals(RollingStock.TRAIN_CHANGED_PROPERTY)) {
958            enableDestinationFields(!locationUnknownCheckBox.isSelected());
959        }
960        if (e.getPropertyName().equals(CarAttributeEditFrame.DISPOSE)) {
961            cef = null;
962        }
963    }
964
965    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CarSetFrame.class);
966}