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