001package jmri.jmrit.operations;
002
003import java.awt.*;
004import java.awt.event.ActionEvent;
005import java.beans.PropertyChangeEvent;
006import java.beans.PropertyChangeListener;
007import java.text.MessageFormat;
008import java.util.*;
009import java.util.List;
010import java.util.concurrent.ConcurrentHashMap;
011
012import javax.swing.*;
013
014import jmri.InstanceManager;
015import jmri.jmrit.operations.locations.Location;
016import jmri.jmrit.operations.locations.Track;
017import jmri.jmrit.operations.rollingstock.RollingStock;
018import jmri.jmrit.operations.rollingstock.cars.Car;
019import jmri.jmrit.operations.rollingstock.cars.CarManager;
020import jmri.jmrit.operations.rollingstock.cars.gui.CarSetFrame;
021import jmri.jmrit.operations.rollingstock.cars.gui.CarsTableFrame;
022import jmri.jmrit.operations.rollingstock.engines.Engine;
023import jmri.jmrit.operations.rollingstock.engines.EngineManager;
024import jmri.jmrit.operations.rollingstock.engines.gui.EngineSetFrame;
025import jmri.jmrit.operations.routes.Route;
026import jmri.jmrit.operations.routes.RouteLocation;
027import jmri.jmrit.operations.setup.Control;
028import jmri.jmrit.operations.setup.Setup;
029import jmri.jmrit.operations.trains.*;
030import jmri.jmrit.operations.trains.trainbuilder.TrainCommon;
031import jmri.util.swing.JmriJOptionPane;
032
033/**
034 * Common elements for the Conductor and Yardmaster Frames.
035 *
036 * @author Dan Boudreau Copyright (C) 2013
037 */
038public abstract class CommonConductorYardmasterPanel extends OperationsPanel implements PropertyChangeListener {
039
040    protected static final boolean IS_MANIFEST = true;
041
042    protected static final String Tab = "    "; // used to space out headers
043    protected static final String Space = " "; // used to pad out panels
044
045    protected Location _location = null;
046    protected Train _train = null;
047
048    protected TrainManager trainManager = InstanceManager.getDefault(TrainManager.class);
049    protected EngineManager engManager = InstanceManager.getDefault(EngineManager.class);
050    protected CarManager carManager = InstanceManager.getDefault(CarManager.class);
051    protected TrainCommon trainCommon = new TrainCommon();
052
053    protected JScrollPane locoPane;
054    protected JScrollPane pickupPane;
055    protected JScrollPane setoutPane;
056    protected JScrollPane movePane;
057
058    // labels
059    protected JLabel textRailRoadName = new JLabel();
060    protected JLabel textTrainDescription = new JLabel();
061    protected JLabel textLocationName = new JLabel();
062    protected JLabel textStatus = new JLabel();
063
064    // major buttons
065    public JButton selectButton = new JButton(Bundle.getMessage("SelectAll"));
066    public JButton clearButton = new JButton(Bundle.getMessage("ClearAll"));
067    public JButton modifyButton = new JButton(Bundle.getMessage("Modify")); // see setModifyButtonText()
068    public JButton moveButton = new JButton(Bundle.getMessage("Move"));
069
070    // text panes
071    protected JTextPane textLocationCommentPane = new JTextPane();
072    protected JTextPane textTrainCommentPane = new JTextPane();
073    protected JTextPane textTrainRouteCommentPane = new JTextPane();
074    protected JTextPane textTrainRouteLocationCommentPane = new JTextPane();
075    protected JTextPane textSwitchListCommentPane = new JTextPane();
076    protected JTextPane textTrainStatusPane = new JTextPane();
077
078    // panels
079    protected JPanel pRailRoadName = new JPanel();
080
081    protected JPanel pTrainDescription = new JPanel();
082
083    protected JPanel pLocationName = new JPanel();
084
085    protected JPanel pTrackComments = new JPanel();
086
087    protected JPanel pLocos = new JPanel();
088    protected JPanel pPickupLocos = new JPanel();
089    protected JPanel pSetoutLocos = new JPanel();
090
091    protected JPanel pPickups = new JPanel();
092    protected JPanel pSetouts = new JPanel();
093    protected JPanel pWorkPanes = new JPanel(); // place car pick ups and set outs side by side using two columns
094    protected JPanel pMoves = new JPanel();
095
096    protected JPanel pStatus = new JPanel();
097    protected JPanel pButtons = new JPanel();
098
099    // check boxes
100    protected ConcurrentHashMap<String, JCheckBox> checkBoxes = new ConcurrentHashMap<>();
101    protected List<RollingStock> rollingStock = Collections.synchronizedList(new ArrayList<>());
102
103    // flags
104    protected boolean isSetMode = false; // when true, cars that aren't selected (checkbox) can be "set"
105
106    public CommonConductorYardmasterPanel() {
107        super();
108        initComponents();
109    }
110
111    public void initComponents() {
112
113        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
114
115        locoPane = new JScrollPane(pLocos);
116        locoPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Engines")));
117
118        pickupPane = new JScrollPane(pPickups);
119        pickupPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Pickup")));
120
121        setoutPane = new JScrollPane(pSetouts);
122        setoutPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("SetOut")));
123
124        movePane = new JScrollPane(pMoves);
125        movePane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("LocalMoves")));
126
127        // Set up the panels
128        pTrackComments.setLayout(new BoxLayout(pTrackComments, BoxLayout.Y_AXIS));
129        pPickupLocos.setLayout(new BoxLayout(pPickupLocos, BoxLayout.Y_AXIS));
130        pSetoutLocos.setLayout(new BoxLayout(pSetoutLocos, BoxLayout.Y_AXIS));
131        pPickups.setLayout(new BoxLayout(pPickups, BoxLayout.Y_AXIS));
132        pSetouts.setLayout(new BoxLayout(pSetouts, BoxLayout.Y_AXIS));
133        pMoves.setLayout(new BoxLayout(pMoves, BoxLayout.Y_AXIS));
134
135        // railroad name
136        pRailRoadName.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("RailroadName")));
137        pRailRoadName.add(textRailRoadName);
138
139        // location name
140        pLocationName.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Location")));
141        pLocationName.add(textLocationName);
142
143        // location comment
144        textLocationCommentPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("LocationComment")));
145        textLocationCommentPane.setBackground(null);
146        textLocationCommentPane.setEditable(false);
147        textLocationCommentPane.setMaximumSize(new Dimension(2000, 200));
148
149        // train description
150        pTrainDescription.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Description")));
151        pTrainDescription.add(textTrainDescription);
152
153        // train comment
154        textTrainCommentPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TrainComment")));
155        textTrainCommentPane.setBackground(null);
156        textTrainCommentPane.setEditable(false);
157        textTrainCommentPane.setMaximumSize(new Dimension(2000, 200));
158
159        // train route comment
160        textTrainRouteCommentPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("RouteComment")));
161        textTrainRouteCommentPane.setBackground(null);
162        textTrainRouteCommentPane.setEditable(false);
163        textTrainRouteCommentPane.setMaximumSize(new Dimension(2000, 200));
164
165        // train route location comment
166        textTrainRouteLocationCommentPane
167                .setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("RouteLocationComment")));
168        textTrainRouteLocationCommentPane.setBackground(null);
169        textTrainRouteLocationCommentPane.setEditable(false);
170        textTrainRouteLocationCommentPane.setMaximumSize(new Dimension(2000, 200));
171
172        // Switch list location comment
173        textSwitchListCommentPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Comment")));
174        textSwitchListCommentPane.setBackground(null);
175        textSwitchListCommentPane.setEditable(false);
176        textSwitchListCommentPane.setMaximumSize(new Dimension(2000, 200));
177
178        // Train status
179        textTrainStatusPane.setBorder(BorderFactory.createTitledBorder(""));
180        textTrainStatusPane.setBackground(null);
181        textTrainStatusPane.setEditable(false);
182        textTrainStatusPane.setMaximumSize(new Dimension(2000, 200));
183
184        // row 12
185        pLocos.setLayout(new BoxLayout(pLocos, BoxLayout.Y_AXIS));
186        pWorkPanes.setLayout(new BoxLayout(pWorkPanes, BoxLayout.Y_AXIS));
187
188        pLocos.add(pPickupLocos);
189        pLocos.add(pSetoutLocos);
190        pWorkPanes.add(pickupPane);
191        pWorkPanes.add(setoutPane);
192
193        // row 13
194        pStatus.setLayout(new GridBagLayout());
195        pStatus.setBorder(BorderFactory.createTitledBorder(""));
196        addItem(pStatus, textStatus, 0, 0);
197
198        // row 14
199        pButtons.setLayout(new GridBagLayout());
200        pButtons.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Work")));
201        addItem(pButtons, selectButton, 0, 0);
202        addItem(pButtons, clearButton, 1, 0);
203        addItem(pButtons, modifyButton, 2, 0);
204
205        // setup buttons
206        addButtonAction(selectButton);
207        addButtonAction(clearButton);
208        addButtonAction(modifyButton);
209    }
210
211    // Select, Clear, and Set Buttons
212    @Override
213    public void buttonActionPerformed(ActionEvent ae) {
214        if (ae.getSource() == selectButton) {
215            selectCheckboxes(true);
216        }
217        if (ae.getSource() == clearButton) {
218            selectCheckboxes(false);
219        }
220        if (ae.getSource() == modifyButton) {
221            isSetMode = !isSetMode; // toggle setMode
222            update();
223            // ask if user wants to add cars to train
224            if (isSetMode) {
225                addCarToTrain();
226            }
227        }
228        check();
229    }
230
231    protected void initialize() {
232        removePropertyChangeListerners();
233        pTrackComments.removeAll();
234        pPickupLocos.removeAll();
235        pSetoutLocos.removeAll();
236        pPickups.removeAll();
237        pSetouts.removeAll();
238        pMoves.removeAll();
239
240        // turn everything off and re-enable if needed
241        pWorkPanes.setVisible(false);
242        pickupPane.setVisible(false);
243        setoutPane.setVisible(false);
244        locoPane.setVisible(false);
245        pPickupLocos.setVisible(false);
246        pSetoutLocos.setVisible(false);
247        movePane.setVisible(false);
248
249        textTrainRouteLocationCommentPane.setVisible(false);
250
251        setModifyButtonText();
252    }
253
254    protected void updateComplete() {
255        pTrackComments.repaint();
256        pPickupLocos.repaint();
257        pSetoutLocos.repaint();
258        pPickups.repaint();
259        pSetouts.repaint();
260        pMoves.repaint();
261
262        pTrackComments.revalidate();
263        pPickupLocos.revalidate();
264        pSetoutLocos.revalidate();
265        pPickups.revalidate();
266        pSetouts.revalidate();
267        pMoves.revalidate();
268
269        selectButton.setEnabled(!checkBoxes.isEmpty() && !isSetMode);
270        clearButton.setEnabled(!checkBoxes.isEmpty() && !isSetMode);
271        check();
272
273        log.debug("update complete");
274    }
275
276    private void addCarToTrain() {
277        if (JmriJOptionPane.showConfirmDialog(this,
278                Bundle.getMessage("WantAddCarsToTrain?", _train.getName()),
279                Bundle.getMessage("AddCarsToTrain?"), JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) {
280            new CarsTableFrame(false, textLocationName.getText(), null);
281        }
282    }
283
284    CarSetFrame csf = null;
285
286    // action for set button for a car, opens the set car window
287    public void carSetButtonActionPerfomed(ActionEvent ae) {
288        String name = ((JButton) ae.getSource()).getName();
289        log.debug("Set button for car {}", name);
290        Car car = carManager.getById(name);
291        if (csf != null) {
292            csf.dispose();
293        }
294        csf = new CarSetFrame();
295        csf.initComponents();
296        csf.load(car);
297    }
298
299    EngineSetFrame esf = null;
300
301    // action for set button for an engine, opens the set engine window
302    public void engineSetButtonActionPerfomed(ActionEvent ae) {
303        String name = ((JButton) ae.getSource()).getName();
304        log.debug("Set button for loco {}", name);
305        Engine eng = engManager.getById(name);
306        if (esf != null) {
307            esf.dispose();
308        }
309        esf = new EngineSetFrame();
310        esf.initComponents();
311        esf.load(eng);
312    }
313
314    // confirm that all work is done
315    @Override
316    protected void checkBoxActionPerformed(ActionEvent ae) {
317        check();
318    }
319
320    // Determines if all car checkboxes are selected. Disables the Set button if
321    // all checkbox are selected.
322    protected void check() {
323        Enumeration<JCheckBox> en = checkBoxes.elements();
324        while (en.hasMoreElements()) {
325            JCheckBox checkBox = en.nextElement();
326            if (!checkBox.isSelected()) {
327                // log.debug("Checkbox (" + checkBox.getText() + ") isn't selected ");
328                moveButton.setEnabled(false);
329                modifyButton.setEnabled(true);
330                return;
331            }
332        }
333        // all selected, work done!
334        moveButton.setEnabled(_train != null && _train.isBuilt());
335        modifyButton.setEnabled(false);
336        isSetMode = false;
337        setModifyButtonText();
338    }
339
340    protected void selectCheckboxes(boolean enable) {
341        Enumeration<JCheckBox> en = checkBoxes.elements();
342        while (en.hasMoreElements()) {
343            JCheckBox checkBox = en.nextElement();
344            checkBox.setSelected(enable);
345        }
346        isSetMode = false;
347    }
348
349    protected void loadTrainDescription() {
350        textTrainDescription.setText(TrainCommon.getOnlyText(_train.getDescription()));
351        textTrainDescription.setForeground(TrainCommon.getTextColor(_train.getDescription()));
352    }
353
354    /**
355     * show train comment box only if there's a comment
356     */
357    protected void loadTrainComment() {
358        if (_train.getComment().equals(Train.NONE)) {
359            textTrainCommentPane.setVisible(false);
360        } else {
361            textTrainCommentPane.setVisible(true);
362            textTrainCommentPane.setText(_train.getComment());
363            textTrainCommentPane.setForeground(TrainCommon.getTextColor(_train.getCommentWithColor()));
364        }
365    }
366
367    protected void loadRailroadName() {
368        // Does this train have a unique railroad name?
369        if (!_train.getRailroadName().equals(Train.NONE)) {
370            textRailRoadName.setText(TrainCommon.getOnlyText(_train.getRailroadName()));
371            textRailRoadName.setForeground(TrainCommon.getTextColor(_train.getRailroadName()));
372        } else {
373            textRailRoadName.setText(Setup.getRailroadName());
374        }
375    }
376
377    protected void loadLocationComment(Location location) {
378        textLocationCommentPane
379                .setVisible(!location.getComment().isEmpty() && Setup.isPrintLocationCommentsEnabled());
380        if (textLocationCommentPane.isVisible()) {
381            textLocationCommentPane.setText(location.getComment());
382            textLocationCommentPane.setForeground(TrainCommon.getTextColor(location.getCommentWithColor()));
383        }
384    }
385
386    protected void loadLocationSwitchListComment(Location location) {
387        textSwitchListCommentPane.setVisible(!location.getSwitchListComment().isEmpty());
388        if (textSwitchListCommentPane.isVisible()) {
389            textSwitchListCommentPane.setText(location.getSwitchListComment());
390            textSwitchListCommentPane.setForeground(TrainCommon.getTextColor(location.getSwitchListCommentWithColor()));
391        }
392    }
393
394    /**
395     * show route comment box only if there's a route comment
396     */
397    protected void loadRouteComment() {
398        if (_train.getRoute() != null && _train.getRoute().getComment().equals(Route.NONE) ||
399                !Setup.isPrintRouteCommentsEnabled()) {
400            textTrainRouteCommentPane.setVisible(false);
401        } else {
402            textTrainRouteCommentPane.setVisible(true);
403            textTrainRouteCommentPane.setText(TrainCommon.getOnlyText(_train.getRoute().getComment()));
404            textTrainRouteCommentPane.setForeground(TrainCommon.getTextColor(_train.getRoute().getComment()));
405        }
406    }
407
408    protected void loadRouteLocationComment(RouteLocation rl) {
409        textTrainRouteLocationCommentPane.setVisible(!rl.getComment().equals(RouteLocation.NONE));
410        if (textTrainRouteLocationCommentPane.isVisible()) {
411            textTrainRouteLocationCommentPane.setText(rl.getComment());
412            textTrainRouteLocationCommentPane.setForeground(rl.getCommentColor());
413        }
414    }
415
416    protected void updateTrackComments(RouteLocation rl, boolean isManifest) {
417        Location location = rl.getLocation();
418        if (location != null) {
419            List<Track> tracks = location.getTracksByNameList(null);
420            for (Track track : tracks) {
421                if (isManifest && !track.isPrintManifestCommentEnabled() ||
422                        !isManifest && !track.isPrintSwitchListCommentEnabled()) {
423                    continue;
424                }
425                // any pick ups or set outs to this track?
426                boolean pickup = false;
427                boolean setout = false;
428                List<Car> carList = carManager.getByTrainDestinationList(_train);
429                for (Car car : carList) {
430                    if (car.getRouteLocation() == rl && car.getTrack() != null && car.getTrack() == track) {
431                        pickup = true;
432                    }
433                    if (car.getRouteDestination() == rl &&
434                            car.getDestinationTrack() != null &&
435                            car.getDestinationTrack() == track) {
436                        setout = true;
437                    }
438                }
439                // display the appropriate comment if there's one
440                if (pickup || setout) {
441                    JTextPane commentTextPane = new JTextPane();
442                    if (pickup && setout && !track.getCommentBoth().equals(Track.NONE)) {
443                        commentTextPane.setText(track.getCommentBoth());
444                        commentTextPane.setForeground(TrainCommon.getTextColor(track.getCommentBothWithColor()));
445                    } else if (pickup && !setout && !track.getCommentPickup().equals(Track.NONE)) {
446                        commentTextPane.setText(track.getCommentPickup());
447                        commentTextPane.setForeground(TrainCommon.getTextColor(track.getCommentPickupWithColor()));
448                    } else if (!pickup && setout && !track.getCommentSetout().equals(Track.NONE)) {
449                        commentTextPane.setText(track.getCommentSetout());
450                        commentTextPane.setForeground(TrainCommon.getTextColor(track.getCommentSetoutWithColor()));
451                    }
452                    if (!commentTextPane.getText().isEmpty()) {
453                        commentTextPane.setBorder(
454                                BorderFactory.createTitledBorder(Bundle.getMessage("Comment") + " " + track.getName()));
455                        commentTextPane.setBackground(null);
456                        commentTextPane.setEditable(false);
457                        commentTextPane.setMaximumSize(new Dimension(2000, 200));
458                        pTrackComments.add(commentTextPane);
459                        pTrackComments.setVisible(true);
460                    }
461                }
462            }
463        }
464    }
465
466    /**
467     * Uses "ep" prefix to denote a checkbox with an engine pick up, and "es"
468     * for an engine set out.
469     *
470     * @param rl The routeLocation to show loco pick ups or set outs.
471     */
472    protected void updateLocoPanes(RouteLocation rl) {
473        if (Setup.isPrintHeadersEnabled()) {
474            JLabel header = new JLabel(Tab + trainCommon.getPickupEngineHeader(!TrainCommon.IS_TWO_COLUMN_TRACK));
475            setLabelFont(header);
476            pPickupLocos.add(header);
477            JLabel headerDrop = new JLabel(Tab + trainCommon.getDropEngineHeader(!TrainCommon.IS_TWO_COLUMN_TRACK));
478            setLabelFont(headerDrop);
479            pSetoutLocos.add(headerDrop);
480        }
481        // check for locos
482        List<Engine> engList = engManager.getByTrainBlockingList(_train);
483        for (Engine engine : engList) {
484            if (engine.getRouteLocation() == rl && engine.getTrack() != null) {
485                locoPane.setVisible(true);
486                pPickupLocos.setVisible(true);
487                rollingStock.add(engine);
488                engine.addPropertyChangeListener(this);
489                JCheckBox checkBox;
490                if (checkBoxes.containsKey("ep" + engine.getId())) {
491                    checkBox = checkBoxes.get("ep" + engine.getId());
492                } else {
493                    checkBox = new JCheckBox(trainCommon.pickupEngine(engine, !TrainCommon.IS_TWO_COLUMN_TRACK));
494                    setCheckBoxFont(checkBox, Setup.getPickupEngineColor());
495                    addCheckBoxAction(checkBox);
496                    checkBoxes.put("ep" + engine.getId(), checkBox);
497                }
498                if (isSetMode && !checkBox.isSelected()) {
499                    pPickupLocos.add(addSet(engine));
500                } else {
501                    pPickupLocos.add(checkBox);
502                }
503            }
504            if (engine.getRouteDestination() == rl) {
505                locoPane.setVisible(true);
506                pSetoutLocos.setVisible(true);
507                rollingStock.add(engine);
508                engine.addPropertyChangeListener(this);
509                JCheckBox checkBox;
510                if (checkBoxes.containsKey("es" + engine.getId())) {
511                    checkBox = checkBoxes.get("es" + engine.getId());
512                } else {
513                    checkBox = new JCheckBox(trainCommon.dropEngine(engine, !TrainCommon.IS_TWO_COLUMN_TRACK));
514                    setCheckBoxFont(checkBox, Setup.getDropEngineColor());
515                    addCheckBoxAction(checkBox);
516                    checkBoxes.put("es" + engine.getId(), checkBox);
517                }
518                if (isSetMode && !checkBox.isSelected()) {
519                    pSetoutLocos.add(addSet(engine));
520                } else {
521                    pSetoutLocos.add(checkBox);
522                }
523            }
524        }
525        // pad the panels in case the horizontal scroll bar appears
526        pPickupLocos.add(new JLabel(Space));
527        pSetoutLocos.add(new JLabel(Space));
528    }
529
530    /**
531     * Block cars by track (optional), then pick up and set out for each
532     * location in a train's route. This shows each car with a check box or with
533     * a set button. The set button is displayed when the checkbox isn't
534     * selected and the display is in "set" mode. If the car is a utility. Show
535     * the number of cars that have the same attributes, and not the car's road
536     * and number. Each car is displayed only once in one of three panes. The
537     * three panes are pick up, set out, or local move. To keep track of each
538     * car and which pane to use, they are placed in the list "rollingStock"
539     * with the prefix "p", "s" or "m" and the car's unique id.
540     *
541     * @param rl         The RouteLocation
542     * @param isManifest True if manifest, false if switch list
543     */
544    protected void blockCars(RouteLocation rl, boolean isManifest) {
545        if (Setup.isPrintHeadersEnabled()) {
546            JLabel header = new JLabel(
547                    Tab + trainCommon.getPickupCarHeader(isManifest, !TrainCommon.IS_TWO_COLUMN_TRACK));
548            setLabelFont(header);
549            pPickups.add(header);
550            header = new JLabel(Tab + trainCommon.getDropCarHeader(isManifest, !TrainCommon.IS_TWO_COLUMN_TRACK));
551            setLabelFont(header);
552            pSetouts.add(header);
553            header = new JLabel(Tab + trainCommon.getLocalMoveHeader(isManifest));
554            setLabelFont(header);
555            pMoves.add(header);
556        }
557        List<Track> tracks = rl.getLocation().getTracksByNameList(null);
558        List<RouteLocation> routeList = _train.getRoute().getBlockingOrder();
559        List<Car> carList = carManager.getByTrainDestinationList(_train);
560        List<Car> carsDone = new ArrayList<>();
561        for (Track track : tracks) {
562            for (RouteLocation rld : routeList) {
563                for (Car car : carList) {
564                    if (carsDone.contains(car)) {
565                        continue;
566                    }
567                    // note that a car in train doesn't have a track assignment
568                    if (car.getTrack() == null) {
569                        continue;
570                    }
571                    // do local move later
572                    if (car.isLocalMove() && rl == rld) {
573                        continue;
574                    }
575                    if (Setup.isSortByTrackNameEnabled() &&
576                            !car.getTrack().getSplitName().equals(track.getSplitName())) {
577                        continue;
578                    }
579                    // determine if car is a pick up from the right track
580                    // caboose or FRED is placed at end of the train
581                    // passenger cars are already blocked in the car list
582                    // passenger cars with negative block numbers are placed at
583                    // the front of the train, positive numbers at the end of
584                    // the train.
585                    if (TrainCommon.isNextCar(car, rl, rld)) {
586                        // yes we have a pick up
587                        pWorkPanes.setVisible(true);
588                        pickupPane.setVisible(true);
589                        if (!rollingStock.contains(car)) {
590                            rollingStock.add(car);
591                            car.addPropertyChangeListener(this);
592                        }
593                        // did we already process this car?
594                        if (checkBoxes.containsKey("p" + car.getId())) {
595                            if (isSetMode && !checkBoxes.get("p" + car.getId()).isSelected()) {
596                                // change to set button so user can remove car
597                                // from train
598                                pPickups.add(addSet(car));
599                            } else {
600                                pPickups.add(checkBoxes.get("p" + car.getId()));
601                            }
602                            // figure out the checkbox text, either single car
603                            // or utility
604                        } else {
605                            String text;
606                            if (car.isUtility()) {
607                                text = trainCommon.pickupUtilityCars(carList, car, isManifest,
608                                        !TrainCommon.IS_TWO_COLUMN_TRACK);
609                                if (text == null) {
610                                    continue; // this car type has already been processed
611                                }
612                            } else {
613                                text = trainCommon.pickupCar(car, isManifest, !TrainCommon.IS_TWO_COLUMN_TRACK);
614                            }
615                            JCheckBox checkBox = new JCheckBox(text);
616                            setCheckBoxFont(checkBox, Setup.getPickupColor());
617                            addCheckBoxAction(checkBox);
618                            pPickups.add(checkBox);
619                            checkBoxes.put("p" + car.getId(), checkBox);
620                        }
621                        carsDone.add(car);
622                    }
623                }
624            }
625            // set outs and local moves
626            for (Car car : carList) {
627                if (carsDone.contains(car)) {
628                    continue;
629                }
630                if (car.getRouteDestination() != rl || car.getDestinationTrack() == null) {
631                    continue;
632                }
633                // car in train if track null, second check is for yard master window
634                if (car.getTrack() == null || car.getTrack() != null && (car.getRouteLocation() != rl)) {
635                    if (Setup.isSortByTrackNameEnabled() &&
636                            !car.getDestinationTrack().getName().equals(track.getName())) {
637                        continue;
638                    }
639                    // we have set outs
640                    pWorkPanes.setVisible(true);
641                    setoutPane.setVisible(true);
642                    if (!rollingStock.contains(car)) {
643                        rollingStock.add(car);
644                        car.addPropertyChangeListener(this);
645                    }
646                    if (checkBoxes.containsKey("s" + car.getId())) {
647                        if (isSetMode && !checkBoxes.get("s" + car.getId()).isSelected()) {
648                            // change to set button so user can remove car from train
649                            pSetouts.add(addSet(car));
650                        } else {
651                            pSetouts.add(checkBoxes.get("s" + car.getId()));
652                        }
653                    } else {
654                        String text;
655                        if (car.isUtility()) {
656                            text = trainCommon.setoutUtilityCars(carList, car, !TrainCommon.LOCAL, isManifest);
657                            if (text == null) {
658                                continue; // this car type has already been processed
659                            }
660                        } else {
661                            text = trainCommon.dropCar(car, isManifest, !TrainCommon.IS_TWO_COLUMN_TRACK);
662                        }
663                        JCheckBox checkBox = new JCheckBox(text);
664                        setCheckBoxFont(checkBox, Setup.getDropColor());
665                        addCheckBoxAction(checkBox);
666                        pSetouts.add(checkBox);
667                        checkBoxes.put("s" + car.getId(), checkBox);
668                    }
669                    // local move?
670                } else if (car.getTrack() != null &&
671                        car.getRouteLocation() == rl &&
672                        (!Setup.isSortByTrackNameEnabled() ||
673                                car.getTrack().getSplitName().equals(track.getSplitName()))) {
674                    movePane.setVisible(true);
675                    if (!rollingStock.contains(car)) {
676                        rollingStock.add(car);
677                        car.addPropertyChangeListener(this);
678                    }
679                    if (checkBoxes.containsKey("m" + car.getId())) {
680                        if (isSetMode && !checkBoxes.get("m" + car.getId()).isSelected()) {
681                            // change to set button so user can remove car from train
682                            pMoves.add(addSet(car));
683                        } else {
684                            pMoves.add(checkBoxes.get("m" + car.getId()));
685                        }
686                    } else {
687                        String text;
688                        if (car.isUtility()) {
689                            text = trainCommon.setoutUtilityCars(carList, car, TrainCommon.LOCAL, isManifest);
690                            if (text == null) {
691                                continue; // this car type has already been processed
692                            }
693                        } else {
694                            text = trainCommon.localMoveCar(car, isManifest);
695                        }
696                        JCheckBox checkBox = new JCheckBox(text);
697                        setCheckBoxFont(checkBox, Setup.getLocalColor());
698                        addCheckBoxAction(checkBox);
699                        pMoves.add(checkBox);
700                        checkBoxes.put("m" + car.getId(), checkBox);
701                    }
702                    carsDone.add(car);
703                }
704            }
705            // if not sorting by track, we're done
706            if (!Setup.isSortByTrackNameEnabled()) {
707                break;
708            }
709        }
710        // pad the panels in case the horizontal scroll bar appears
711        pPickups.add(new JLabel(Space));
712        pSetouts.add(new JLabel(Space));
713        pMoves.add(new JLabel(Space));
714    }
715
716    // replace the car or engine checkbox and text with only the road and number and
717    // a Set button
718    protected JPanel addSet(RollingStock rs) {
719        JPanel pSet = new JPanel();
720        pSet.setLayout(new GridBagLayout());
721        JButton setButton = new JButton(Bundle.getMessage("Set"));
722        setButton.setToolTipText(Bundle.getMessage("SetButtonToolTip"));
723        setButton.setName(rs.getId());
724        setButton.addActionListener((ActionEvent e) -> {
725            if (Car.class.isInstance(rs)) {
726                carSetButtonActionPerfomed(e);
727            } else {
728                engineSetButtonActionPerfomed(e);
729            }
730        });
731        JLabel label = new JLabel(TrainCommon.padString(rs.toString(),
732                Control.max_len_string_attibute + Control.max_len_string_road_number));
733        setLabelFont(label);
734        addItem(pSet, label, 0, 0);
735        addItemLeft(pSet, setButton, 1, 0);
736        pSet.setAlignmentX(LEFT_ALIGNMENT);
737        return pSet;
738    }
739
740    protected void setCheckBoxFont(JCheckBox checkBox, Color color) {
741        if (Setup.isTabEnabled()) {
742            Font font = new Font(Setup.getFontName(), Font.PLAIN, checkBox.getFont().getSize());
743            checkBox.setFont(font);
744            checkBox.setForeground(color);
745        }
746    }
747
748    protected void setLabelFont(JLabel label) {
749        if (Setup.isTabEnabled()) {
750            Font font = new Font(Setup.getFontName(), Font.PLAIN, label.getFont().getSize());
751            label.setFont(font);
752        }
753    }
754
755    protected void setModifyButtonText() {
756        if (isSetMode) {
757            modifyButton.setText(Bundle.getMessage("Done"));
758        } else {
759            modifyButton.setText(Bundle.getMessage("Modify"));
760        }
761    }
762
763    // returns departure strings for a train
764    protected String getStatus(RouteLocation rl, boolean isManifest) {
765        String text = ""; // user modified text
766        String status = "";
767        try {
768            if (rl == _train.getTrainTerminatesRouteLocation()) {
769                status = MessageFormat.format(text = TrainManifestText.getStringTrainTerminates(),
770                        new Object[]{_train.getTrainTerminatesName(),
771                                _train.getSplitName(), _train.getDescription(),
772                                rl.getLocation().getDivisionName()});
773            } else if (rl != _train.getCurrentRouteLocation() &&
774                    _train.getExpectedArrivalTime(rl).equals(Train.ALREADY_SERVICED)) {
775                status = MessageFormat.format(text = TrainSwitchListText.getStringTrainDone(),
776                        new Object[]{_train.getSplitName(), _train.getDescription(),
777                                rl.getSplitName()});
778            } else if (!_train.isBuilt() || rl == null) {
779                status = _train.getStatus();
780            } else if (Setup.isPrintLoadsAndEmptiesEnabled()) {
781                int emptyCars = _train.getNumberEmptyCarsInTrain(rl);
782                if (isManifest) {
783                    text = TrainManifestText.getStringTrainDepartsLoads();
784                } else {
785                    text = TrainSwitchListText.getStringTrainDepartsLoads();
786                }
787                status = MessageFormat.format(text,
788                        new Object[]{rl.getSplitName(), rl.getTrainDirectionString(),
789                                _train.getNumberCarsInTrain(rl) - emptyCars, emptyCars, _train.getTrainLength(rl),
790                                Setup.getLengthUnit().toLowerCase(), _train.getTrainWeight(rl),
791                                _train.getTrainTerminatesName(),
792                                _train.getSplitName()});
793            } else {
794                if (isManifest) {
795                    text = TrainManifestText.getStringTrainDepartsCars();
796                } else {
797                    text = TrainSwitchListText.getStringTrainDepartsCars();
798                }
799                status = MessageFormat.format(text,
800                        new Object[]{rl.getSplitName(), rl.getTrainDirectionString(),
801                                _train.getNumberCarsInTrain(rl), _train.getTrainLength(rl),
802                                Setup.getLengthUnit().toLowerCase(), _train.getTrainWeight(rl),
803                                _train.getTrainTerminatesName(),
804                                _train.getSplitName()});
805            }
806        } catch (IllegalArgumentException e) {
807            log.error("Illegal argument", e);
808            return Bundle.getMessage("ErrorIllegalArgument", text, e.getLocalizedMessage());
809        }
810        _color = TrainCommon.getTextColor(status);
811        return TrainCommon.getOnlyText(status);
812    }
813    
814    private Color _color;
815    protected Color getStatusColor() {
816        return _color;
817    }
818
819    protected void removeCarFromList(Car car) {
820        checkBoxes.remove("p" + car.getId());
821        checkBoxes.remove("s" + car.getId());
822        checkBoxes.remove("m" + car.getId());
823        log.debug("Car ({}) removed from list", car.toString());
824        if (car.isUtility()) {
825            clearAndUpdate(); // need to recalculate number of utility cars
826        }
827    }
828
829    protected void clearAndUpdate() {
830        trainCommon.clearUtilityCarTypes(); // reset the utility car counts
831        checkBoxes.clear();
832        isSetMode = false;
833        update();
834    }
835
836    // to be overridden
837    protected abstract void update();
838
839    protected void removePropertyChangeListerners() {
840        rollingStock.stream().forEach((rs) -> {
841            rs.removePropertyChangeListener(this);
842        });
843        rollingStock.clear();
844    }
845
846    @Override
847    public void dispose() {
848        _train = null;
849        _location = null;
850    }
851
852    @Override
853    public void propertyChange(PropertyChangeEvent e) {
854        log.debug("Property change {} for: {} old: {} new: {}", e.getPropertyName(), e.getSource(), e.getOldValue(),
855                e.getNewValue()); // NOI18N
856    }
857
858    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CommonConductorYardmasterPanel.class);
859}