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