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