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