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.cars.gui.CarSetFrame;
020import jmri.jmrit.operations.rollingstock.cars.gui.CarsTableFrame;
021import jmri.jmrit.operations.rollingstock.engines.*;
022import jmri.jmrit.operations.rollingstock.engines.gui.EngineSetFrame;
023import jmri.jmrit.operations.routes.Route;
024import jmri.jmrit.operations.routes.RouteLocation;
025import jmri.jmrit.operations.setup.Control;
026import jmri.jmrit.operations.setup.Setup;
027import jmri.jmrit.operations.trains.*;
028import jmri.jmrit.operations.trains.trainbuilder.TrainCommon;
029import jmri.util.swing.JmriJOptionPane;
030
031/**
032 * Common elements for the Conductor and Yardmaster Frames.
033 *
034 * @author Dan Boudreau Copyright (C) 2013
035 *
036 */
037public abstract class CommonConductorYardmasterPanel extends OperationsPanel implements PropertyChangeListener {
038
039    protected static final boolean IS_MANIFEST = true;
040
041    protected static final String Tab = "    "; // used to space out headers
042    protected static final String Space = " "; // used to pad out panels
043
044    protected Location _location = null;
045    protected Train _train = null;
046
047    protected TrainManager trainManager = InstanceManager.getDefault(TrainManager.class);
048    protected EngineManager engManager = InstanceManager.getDefault(EngineManager.class);
049    protected CarManager carManager = InstanceManager.getDefault(CarManager.class);
050    protected TrainCommon trainCommon = new TrainCommon();
051
052    protected JScrollPane locoPane;
053    protected JScrollPane pickupPane;
054    protected JScrollPane setoutPane;
055    protected JScrollPane movePane;
056
057    // labels
058    protected JLabel textRailRoadName = new JLabel();
059    protected JLabel textTrainDescription = new JLabel();
060    protected JLabel textLocationName = new JLabel();
061    protected JLabel textStatus = new JLabel();
062
063    // major buttons
064    public JButton selectButton = new JButton(Bundle.getMessage("SelectAll"));
065    public JButton clearButton = new JButton(Bundle.getMessage("ClearAll"));
066    public JButton modifyButton = new JButton(Bundle.getMessage("Modify")); // see setModifyButtonText()
067    public JButton moveButton = new JButton(Bundle.getMessage("Move"));
068
069    // text panes
070    protected JTextPane textLocationCommentPane = new JTextPane();
071    protected JTextPane textTrainCommentPane = new JTextPane();
072    protected JTextPane textTrainRouteCommentPane = new JTextPane();
073    protected JTextPane textTrainRouteLocationCommentPane = new JTextPane();
074    protected JTextPane textSwitchListCommentPane = new JTextPane();
075    protected JTextPane textTrainStatusPane = new JTextPane();
076
077    // panels
078    protected JPanel pRailRoadName = new JPanel();
079
080    protected JPanel pTrainDescription = new JPanel();
081
082    protected JPanel pLocationName = new JPanel();
083
084    protected JPanel pTrackComments = new JPanel();
085
086    protected JPanel pLocos = new JPanel();
087    protected JPanel pPickupLocos = new JPanel();
088    protected JPanel pSetoutLocos = new JPanel();
089
090    protected JPanel pPickups = new JPanel();
091    protected JPanel pSetouts = new JPanel();
092    protected JPanel pWorkPanes = new JPanel(); // place car pick ups and set outs side by side using two columns
093    protected JPanel pMoves = new JPanel();
094
095    protected JPanel pStatus = new JPanel();
096    protected JPanel pButtons = new JPanel();
097
098    // check boxes
099    protected ConcurrentHashMap<String, JCheckBox> checkBoxes = new ConcurrentHashMap<>();
100    protected List<RollingStock> rollingStock = Collections.synchronizedList(new ArrayList<>());
101
102    // flags
103    protected boolean isSetMode = false; // when true, cars that aren't selected (checkbox) can be "set"
104
105    public CommonConductorYardmasterPanel() {
106        super();
107        initComponents();
108    }
109
110    public void initComponents() {
111
112        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
113
114        locoPane = new JScrollPane(pLocos);
115        locoPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Engines")));
116
117        pickupPane = new JScrollPane(pPickups);
118        pickupPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Pickup")));
119
120        setoutPane = new JScrollPane(pSetouts);
121        setoutPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("SetOut")));
122
123        movePane = new JScrollPane(pMoves);
124        movePane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("LocalMoves")));
125
126        // Set up the panels
127        pTrackComments.setLayout(new BoxLayout(pTrackComments, BoxLayout.Y_AXIS));
128        pPickupLocos.setLayout(new BoxLayout(pPickupLocos, BoxLayout.Y_AXIS));
129        pSetoutLocos.setLayout(new BoxLayout(pSetoutLocos, BoxLayout.Y_AXIS));
130        pPickups.setLayout(new BoxLayout(pPickups, BoxLayout.Y_AXIS));
131        pSetouts.setLayout(new BoxLayout(pSetouts, BoxLayout.Y_AXIS));
132        pMoves.setLayout(new BoxLayout(pMoves, BoxLayout.Y_AXIS));
133
134        // railroad name
135        pRailRoadName.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("RailroadName")));
136        pRailRoadName.add(textRailRoadName);
137
138        // location name
139        pLocationName.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Location")));
140        pLocationName.add(textLocationName);
141
142        // location comment
143        textLocationCommentPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("LocationComment")));
144        textLocationCommentPane.setBackground(null);
145        textLocationCommentPane.setEditable(false);
146        textLocationCommentPane.setMaximumSize(new Dimension(2000, 200));
147
148        // train description
149        pTrainDescription.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Description")));
150        pTrainDescription.add(textTrainDescription);
151
152        // train comment
153        textTrainCommentPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TrainComment")));
154        textTrainCommentPane.setBackground(null);
155        textTrainCommentPane.setEditable(false);
156        textTrainCommentPane.setMaximumSize(new Dimension(2000, 200));
157
158        // train route comment
159        textTrainRouteCommentPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("RouteComment")));
160        textTrainRouteCommentPane.setBackground(null);
161        textTrainRouteCommentPane.setEditable(false);
162        textTrainRouteCommentPane.setMaximumSize(new Dimension(2000, 200));
163
164        // train route location comment
165        textTrainRouteLocationCommentPane
166                .setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("RouteLocationComment")));
167        textTrainRouteLocationCommentPane.setBackground(null);
168        textTrainRouteLocationCommentPane.setEditable(false);
169        textTrainRouteLocationCommentPane.setMaximumSize(new Dimension(2000, 200));
170        
171        // Switch list location comment
172        textSwitchListCommentPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Comment")));
173        textSwitchListCommentPane.setBackground(null);
174        textSwitchListCommentPane.setEditable(false);
175        textSwitchListCommentPane.setMaximumSize(new Dimension(2000, 200));
176        
177        // Train status
178        textTrainStatusPane.setBorder(BorderFactory.createTitledBorder(""));
179        textTrainStatusPane.setBackground(null);
180        textTrainStatusPane.setEditable(false);
181        textTrainStatusPane.setMaximumSize(new Dimension(2000, 200));
182
183        // row 12
184        pLocos.setLayout(new BoxLayout(pLocos, BoxLayout.Y_AXIS));
185        pWorkPanes.setLayout(new BoxLayout(pWorkPanes, BoxLayout.Y_AXIS));
186
187        pLocos.add(pPickupLocos);
188        pLocos.add(pSetoutLocos);
189        pWorkPanes.add(pickupPane);
190        pWorkPanes.add(setoutPane);
191
192        // row 13
193        pStatus.setLayout(new GridBagLayout());
194        pStatus.setBorder(BorderFactory.createTitledBorder(""));
195        addItem(pStatus, textStatus, 0, 0);
196
197        // row 14
198        pButtons.setLayout(new GridBagLayout());
199        pButtons.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Work")));
200        addItem(pButtons, selectButton, 0, 0);
201        addItem(pButtons, clearButton, 1, 0);
202        addItem(pButtons, modifyButton, 2, 0);
203
204        // setup buttons
205        addButtonAction(selectButton);
206        addButtonAction(clearButton);
207        addButtonAction(modifyButton);
208    }
209
210    // Select, Clear, and Set Buttons
211    @Override
212    public void buttonActionPerformed(ActionEvent ae) {
213        if (ae.getSource() == selectButton) {
214            selectCheckboxes(true);
215        }
216        if (ae.getSource() == clearButton) {
217            selectCheckboxes(false);
218        }
219        if (ae.getSource() == modifyButton) {
220            isSetMode = !isSetMode; // toggle setMode
221            update();
222            // ask if user wants to add cars to train
223            if (isSetMode) {
224                addCarToTrain();
225            }
226        }
227        check();
228    }
229
230    protected void initialize() {
231        removePropertyChangeListerners();
232        pTrackComments.removeAll();
233        pPickupLocos.removeAll();
234        pSetoutLocos.removeAll();
235        pPickups.removeAll();
236        pSetouts.removeAll();
237        pMoves.removeAll();
238
239        // turn everything off and re-enable if needed
240        pWorkPanes.setVisible(false);
241        pickupPane.setVisible(false);
242        setoutPane.setVisible(false);
243        locoPane.setVisible(false);
244        pPickupLocos.setVisible(false);
245        pSetoutLocos.setVisible(false);
246        movePane.setVisible(false);
247
248        textTrainRouteLocationCommentPane.setVisible(false);
249
250        setModifyButtonText();
251    }
252
253    protected void updateComplete() {
254        pTrackComments.repaint();
255        pPickupLocos.repaint();
256        pSetoutLocos.repaint();
257        pPickups.repaint();
258        pSetouts.repaint();
259        pMoves.repaint();
260
261        pTrackComments.revalidate();
262        pPickupLocos.revalidate();
263        pSetoutLocos.revalidate();
264        pPickups.revalidate();
265        pSetouts.revalidate();
266        pMoves.revalidate();
267
268        selectButton.setEnabled(!checkBoxes.isEmpty() && !isSetMode);
269        clearButton.setEnabled(!checkBoxes.isEmpty() && !isSetMode);
270        check();
271
272        log.debug("update complete");
273    }
274
275    private void addCarToTrain() {
276        if (JmriJOptionPane.showConfirmDialog(this,
277                Bundle.getMessage("WantAddCarsToTrain?", _train.getName()),
278                Bundle.getMessage("AddCarsToTrain?"), JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) {
279            new CarsTableFrame(false, _train.getCurrentRouteLocation().getName(), null);
280        }
281    }
282
283    CarSetFrame csf = null;
284
285    // action for set button for a car, opens the set car window
286    public void carSetButtonActionPerfomed(ActionEvent ae) {
287        String name = ((JButton) ae.getSource()).getName();
288        log.debug("Set button for car {}", name);
289        Car car = carManager.getById(name);
290        if (csf != null) {
291            csf.dispose();
292        }
293        csf = new CarSetFrame();
294        csf.initComponents();
295        csf.load(car);
296    }
297
298    EngineSetFrame esf = null;
299
300    // action for set button for an engine, opens the set engine window
301    public void engineSetButtonActionPerfomed(ActionEvent ae) {
302        String name = ((JButton) ae.getSource()).getName();
303        log.debug("Set button for loco {}", name);
304        Engine eng = engManager.getById(name);
305        if (esf != null) {
306            esf.dispose();
307        }
308        esf = new EngineSetFrame();
309        esf.initComponents();
310        esf.load(eng);
311    }
312
313    // confirm that all work is done
314    @Override
315    protected void checkBoxActionPerformed(ActionEvent ae) {
316        check();
317    }
318
319    // Determines if all car checkboxes are selected. Disables the Set button if
320    // all checkbox are selected.
321    protected void check() {
322        Enumeration<JCheckBox> en = checkBoxes.elements();
323        while (en.hasMoreElements()) {
324            JCheckBox checkBox = en.nextElement();
325            if (!checkBox.isSelected()) {
326                // log.debug("Checkbox (" + checkBox.getText() + ") isn't selected ");
327                moveButton.setEnabled(false);
328                modifyButton.setEnabled(true);
329                return;
330            }
331        }
332        // all selected, work done!
333        moveButton.setEnabled(_train != null && _train.isBuilt());
334        modifyButton.setEnabled(false);
335        isSetMode = false;
336        setModifyButtonText();
337    }
338
339    protected void selectCheckboxes(boolean enable) {
340        Enumeration<JCheckBox> en = checkBoxes.elements();
341        while (en.hasMoreElements()) {
342            JCheckBox checkBox = en.nextElement();
343            checkBox.setSelected(enable);
344        }
345        isSetMode = false;
346    }
347
348    protected void loadTrainDescription() {
349        textTrainDescription.setText(TrainCommon.getTextColorString(_train.getDescription()));
350        textTrainDescription.setForeground(TrainCommon.getTextColor(_train.getDescription()));
351    }
352
353    /**
354     * show train comment box only if there's a comment
355     */
356    protected void loadTrainComment() {
357        if (_train.getComment().equals(Train.NONE)) {
358            textTrainCommentPane.setVisible(false);
359        } else {
360            textTrainCommentPane.setVisible(true);
361            textTrainCommentPane.setText(_train.getComment());
362            textTrainCommentPane.setForeground(TrainCommon.getTextColor(_train.getCommentWithColor()));
363        }
364    }
365
366    protected void loadRailroadName() {
367        // Does this train have a unique railroad name?
368        if (!_train.getRailroadName().equals(Train.NONE)) {
369            textRailRoadName.setText(TrainCommon.getTextColorString(_train.getRailroadName()));
370            textRailRoadName.setForeground(TrainCommon.getTextColor(_train.getRailroadName()));
371        } else {
372            textRailRoadName.setText(Setup.getRailroadName());
373        }
374    }
375
376    protected void loadLocationComment(Location location) {
377        textLocationCommentPane
378                .setVisible(!location.getComment().isEmpty() && Setup.isPrintLocationCommentsEnabled());
379        if (textLocationCommentPane.isVisible()) {
380            textLocationCommentPane.setText(location.getComment());
381            textLocationCommentPane.setForeground(TrainCommon.getTextColor(location.getCommentWithColor()));
382        }
383    }
384
385    protected void loadLocationSwitchListComment(Location location) {
386        textSwitchListCommentPane.setVisible(!location.getSwitchListComment().isEmpty());
387        if (textSwitchListCommentPane.isVisible()) {
388            textSwitchListCommentPane.setText(location.getSwitchListComment());
389            textSwitchListCommentPane.setForeground(TrainCommon.getTextColor(location.getSwitchListCommentWithColor()));
390        }
391    }
392
393    /**
394     * show route comment box only if there's a route comment
395     */
396    protected void loadRouteComment() {
397        if (_train.getRoute() != null && _train.getRoute().getComment().equals(Route.NONE) ||
398                !Setup.isPrintRouteCommentsEnabled()) {
399            textTrainRouteCommentPane.setVisible(false);
400        } else {
401            textTrainRouteCommentPane.setVisible(true);
402            textTrainRouteCommentPane.setText(TrainCommon.getTextColorString(_train.getRoute().getComment()));
403            textTrainRouteCommentPane.setForeground(TrainCommon.getTextColor(_train.getRoute().getComment()));
404        }
405    }
406
407    protected void loadRouteLocationComment(RouteLocation rl) {
408        textTrainRouteLocationCommentPane.setVisible(!rl.getComment().equals(RouteLocation.NONE));
409        if (textTrainRouteLocationCommentPane.isVisible()) {
410            textTrainRouteLocationCommentPane.setText(rl.getComment());
411            textTrainRouteLocationCommentPane.setForeground(rl.getCommentColor());
412        }
413    }
414
415    protected void updateTrackComments(RouteLocation rl, boolean isManifest) {
416        Location location = rl.getLocation();
417        if (location != null) {
418            List<Track> tracks = location.getTracksByNameList(null);
419            for (Track track : tracks) {
420                if (isManifest && !track.isPrintManifestCommentEnabled() ||
421                        !isManifest && !track.isPrintSwitchListCommentEnabled()) {
422                    continue;
423                }
424                // any pick ups or set outs to this track?
425                boolean pickup = false;
426                boolean setout = false;
427                List<Car> carList = carManager.getByTrainDestinationList(_train);
428                for (Car car : carList) {
429                    if (car.getRouteLocation() == rl && car.getTrack() != null && car.getTrack() == track) {
430                        pickup = true;
431                    }
432                    if (car.getRouteDestination() == rl &&
433                            car.getDestinationTrack() != null &&
434                            car.getDestinationTrack() == track) {
435                        setout = true;
436                    }
437                }
438                // display the appropriate comment if there's one
439                if (pickup || setout) {
440                    JTextPane commentTextPane = new JTextPane();
441                    if (pickup && setout && !track.getCommentBoth().equals(Track.NONE)) {
442                        commentTextPane.setText(track.getCommentBoth());
443                        commentTextPane.setForeground(TrainCommon.getTextColor(track.getCommentBothWithColor()));
444                    } else if (pickup && !setout && !track.getCommentPickup().equals(Track.NONE)) {
445                        commentTextPane.setText(track.getCommentPickup());
446                        commentTextPane.setForeground(TrainCommon.getTextColor(track.getCommentPickupWithColor()));
447                    } else if (!pickup && setout && !track.getCommentSetout().equals(Track.NONE)) {
448                        commentTextPane.setText(track.getCommentSetout());
449                        commentTextPane.setForeground(TrainCommon.getTextColor(track.getCommentSetoutWithColor()));
450                    }
451                    if (!commentTextPane.getText().isEmpty()) {
452                        commentTextPane.setBorder(
453                                BorderFactory.createTitledBorder(Bundle.getMessage("Comment") + " " + track.getName()));
454                        commentTextPane.setBackground(null);
455                        commentTextPane.setEditable(false);
456                        commentTextPane.setMaximumSize(new Dimension(2000, 200));
457                        pTrackComments.add(commentTextPane);
458                        pTrackComments.setVisible(true);
459                    }
460                }
461            }
462        }
463    }
464
465    /**
466     * Uses "ep" prefix to denote a checkbox with an engine pick up, and "es" for an
467     * engine set out.
468     *
469     * @param rl The routeLocation to show loco pick ups or set outs.
470     */
471    protected void updateLocoPanes(RouteLocation rl) {
472        if (Setup.isPrintHeadersEnabled()) {
473            JLabel header = new JLabel(Tab + trainCommon.getPickupEngineHeader());
474            setLabelFont(header);
475            pPickupLocos.add(header);
476            JLabel headerDrop = new JLabel(Tab + trainCommon.getDropEngineHeader());
477            setLabelFont(headerDrop);
478            pSetoutLocos.add(headerDrop);
479        }
480        // check for locos
481        List<Engine> engList = engManager.getByTrainBlockingList(_train);
482        for (Engine engine : engList) {
483            if (engine.getRouteLocation() == rl && engine.getTrack() != null) {
484                locoPane.setVisible(true);
485                pPickupLocos.setVisible(true);
486                rollingStock.add(engine);
487                engine.addPropertyChangeListener(this);
488                JCheckBox checkBox;
489                if (checkBoxes.containsKey("ep" + engine.getId())) {
490                    checkBox = checkBoxes.get("ep" + engine.getId());
491                } else {
492                    checkBox = new JCheckBox(trainCommon.pickupEngine(engine));
493                    setCheckBoxFont(checkBox, Setup.getPickupEngineColor());
494                    addCheckBoxAction(checkBox);
495                    checkBoxes.put("ep" + engine.getId(), checkBox);
496                }
497                if (isSetMode && !checkBox.isSelected()) {
498                    pPickupLocos.add(addSet(engine));
499                } else {
500                    pPickupLocos.add(checkBox);
501                }
502            }
503            if (engine.getRouteDestination() == rl) {
504                locoPane.setVisible(true);
505                pSetoutLocos.setVisible(true);
506                rollingStock.add(engine);
507                engine.addPropertyChangeListener(this);
508                JCheckBox checkBox;
509                if (checkBoxes.containsKey("es" + engine.getId())) {
510                    checkBox = checkBoxes.get("es" + engine.getId());
511                } else {
512                    checkBox = new JCheckBox(trainCommon.dropEngine(engine));
513                    setCheckBoxFont(checkBox, Setup.getDropEngineColor());
514                    addCheckBoxAction(checkBox);
515                    checkBoxes.put("es" + engine.getId(), checkBox);
516                }
517                if (isSetMode && !checkBox.isSelected()) {
518                    pSetoutLocos.add(addSet(engine));
519                } else {
520                    pSetoutLocos.add(checkBox);
521                }
522            }
523        }
524        // pad the panels in case the horizontal scroll bar appears
525        pPickupLocos.add(new JLabel(Space));
526        pSetoutLocos.add(new JLabel(Space));
527    }
528
529    /**
530     * Block cars by track (optional), then pick up and set out for each location in
531     * a train's route. This shows each car with a check box or with a set button.
532     * The set button is displayed when the checkbox isn't selected and the display
533     * is in "set" mode. If the car is a utility. Show the number of cars that have
534     * the same attributes, and not the car's road and number. Each car is displayed
535     * only once in one of three panes. The three panes are pick up, set out, or
536     * local move. To keep track of each car and which pane to use, they are placed
537     * in the list "rollingStock" with the prefix "p", "s" or "m" and the car's
538     * unique id.
539     *
540     * @param rl         The RouteLocation
541     * @param isManifest True if manifest, false if switch list
542     *
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 one of two possible departure strings for a train
764    protected String getStatus(RouteLocation rl, boolean isManifest) {
765        if (rl == _train.getTrainTerminatesRouteLocation()) {
766            return MessageFormat.format(TrainManifestText.getStringTrainTerminates(),
767                    new Object[] { _train.getTrainTerminatesName() });
768        }
769        if (rl != _train.getCurrentRouteLocation() &&
770                _train.getExpectedArrivalTime(rl).equals(Train.ALREADY_SERVICED)) {
771            return MessageFormat.format(TrainSwitchListText.getStringTrainDone(), new Object[] { _train.getName() });
772        }
773        if (!_train.isBuilt() || rl == null) {
774            return _train.getStatus();
775        }
776        if (Setup.isPrintLoadsAndEmptiesEnabled()) {
777            int emptyCars = _train.getNumberEmptyCarsInTrain(rl);
778            String text;
779            if (isManifest) {
780                text = TrainManifestText.getStringTrainDepartsLoads();
781            } else {
782                text = TrainSwitchListText.getStringTrainDepartsLoads();
783            }
784            return MessageFormat.format(text,
785                    new Object[] { rl.getSplitName(), rl.getTrainDirectionString(),
786                            _train.getNumberCarsInTrain(rl) - emptyCars, emptyCars, _train.getTrainLength(rl),
787                            Setup.getLengthUnit().toLowerCase(), _train.getTrainWeight(rl),
788                            _train.getTrainTerminatesName(), _train.getName() });
789        } else {
790            String text;
791            if (isManifest) {
792                text = TrainManifestText.getStringTrainDepartsCars();
793            } else {
794                text = TrainSwitchListText.getStringTrainDepartsCars();
795            }
796            return MessageFormat.format(text,
797                    new Object[] { rl.getSplitName(), rl.getTrainDirectionString(),
798                            _train.getNumberCarsInTrain(rl), _train.getTrainLength(rl),
799                            Setup.getLengthUnit().toLowerCase(), _train.getTrainWeight(rl),
800                            _train.getTrainTerminatesName(), _train.getName() });
801        }
802    }
803
804    protected void removeCarFromList(Car car) {
805        checkBoxes.remove("p" + car.getId());
806        checkBoxes.remove("s" + car.getId());
807        checkBoxes.remove("m" + car.getId());
808        log.debug("Car ({}) removed from list", car.toString());
809        if (car.isUtility()) {
810            clearAndUpdate(); // need to recalculate number of utility cars
811        }
812    }
813
814    protected void clearAndUpdate() {
815        trainCommon.clearUtilityCarTypes(); // reset the utility car counts
816        checkBoxes.clear();
817        isSetMode = false;
818        update();
819    }
820
821    // to be overridden
822    protected abstract void update();
823
824    protected void removePropertyChangeListerners() {
825        rollingStock.stream().forEach((rs) -> {
826            rs.removePropertyChangeListener(this);
827        });
828        rollingStock.clear();
829    }
830
831    @Override
832    public void dispose() {
833        _train = null;
834        _location = null;
835    }
836
837    @Override
838    public void propertyChange(PropertyChangeEvent e) {
839        log.debug("Property change {} for: {} old: {} new: {}", e.getPropertyName(), e.getSource(), e.getOldValue(),
840                e.getNewValue()); // NOI18N
841    }
842
843    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CommonConductorYardmasterPanel.class);
844}