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