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        for (Track track : tracks) {
551            for (RouteLocation rld : routeList) {
552                for (Car car : carList) {
553                    // note that a car in train doesn't have a track assignment
554                    if (car.getTrack() == null) {
555                        continue;
556                    }
557                    if (car.isLocalMove() && rl == rld) {
558                        continue;
559                    }
560                    // determine if car is a pick up from the right track
561                    // caboose or FRED is placed at end of the train
562                    // passenger cars are already blocked in the car list
563                    // passenger cars with negative block numbers are placed at
564                    // the front of the train, positive numbers at the end of
565                    // the train.
566                    if (TrainCommon.isNextCar(car, rl, rld)) {
567                        // yes we have a pick up
568                        pWorkPanes.setVisible(true);
569                        pickupPane.setVisible(true);
570                        if (!rollingStock.contains(car)) {
571                            rollingStock.add(car);
572                            car.addPropertyChangeListener(this);
573                        }
574                        // did we already process this car?
575                        if (checkBoxes.containsKey("p" + car.getId())) {
576                            if (isSetMode && !checkBoxes.get("p" + car.getId()).isSelected()) {
577                                // change to set button so user can remove car
578                                // from train
579                                pPickups.add(addSet(car));
580                            } else {
581                                pPickups.add(checkBoxes.get("p" + car.getId()));
582                            }
583                            // figure out the checkbox text, either single car
584                            // or utility
585                        } else {
586                            String text;
587                            if (car.isUtility()) {
588                                text = trainCommon.pickupUtilityCars(carList, car, isManifest,
589                                        !TrainCommon.IS_TWO_COLUMN_TRACK);
590                                if (text == null) {
591                                    continue; // this car type has already been processed
592                                }
593                            } else {
594                                text = trainCommon.pickupCar(car, isManifest, !TrainCommon.IS_TWO_COLUMN_TRACK);
595                            }
596                            JCheckBox checkBox = new JCheckBox(text);
597                            setCheckBoxFont(checkBox);
598                            addCheckBoxAction(checkBox);
599                            pPickups.add(checkBox);
600                            checkBoxes.put("p" + car.getId(), checkBox);
601                        }
602                    }
603                }
604            }
605            // set outs and local moves
606            for (Car car : carList) {
607                if (car.getRouteDestination() != rl || car.getDestinationTrack() == null) {
608                    continue;
609                }
610                // car in train if track null, second check is for yard master window
611                if (car.getTrack() == null || car.getTrack() != null && (car.getRouteLocation() != rl)) {
612                    if (Setup.isSortByTrackNameEnabled() &&
613                            !car.getDestinationTrack().getName().equals(track.getName())) {
614                        continue;
615                    }
616                    // we have set outs
617                    pWorkPanes.setVisible(true);
618                    setoutPane.setVisible(true);
619                    if (!rollingStock.contains(car)) {
620                        rollingStock.add(car);
621                        car.addPropertyChangeListener(this);
622                    }
623                    if (checkBoxes.containsKey("s" + car.getId())) {
624                        if (isSetMode && !checkBoxes.get("s" + car.getId()).isSelected()) {
625                            // change to set button so user can remove car from train
626                            pSetouts.add(addSet(car));
627                        } else {
628                            pSetouts.add(checkBoxes.get("s" + car.getId()));
629                        }
630                    } else {
631                        String text;
632                        if (car.isUtility()) {
633                            text = trainCommon.setoutUtilityCars(carList, car, !TrainCommon.LOCAL, isManifest);
634                            if (text == null) {
635                                continue; // this car type has already been processed
636                            }
637                        } else {
638                            text = trainCommon.dropCar(car, isManifest, !TrainCommon.IS_TWO_COLUMN_TRACK);
639                        }
640                        JCheckBox checkBox = new JCheckBox(text);
641                        setCheckBoxFont(checkBox);
642                        addCheckBoxAction(checkBox);
643                        pSetouts.add(checkBox);
644                        checkBoxes.put("s" + car.getId(), checkBox);
645                    }
646                    // local move?
647                } else if (car.getTrack() != null &&
648                        car.getRouteLocation() == rl &&
649                        (!Setup.isSortByTrackNameEnabled() || car.getTrack().getName().equals(track.getName()))) {
650                    movePane.setVisible(true);
651                    if (!rollingStock.contains(car)) {
652                        rollingStock.add(car);
653                        car.addPropertyChangeListener(this);
654                    }
655                    if (checkBoxes.containsKey("m" + car.getId())) {
656                        if (isSetMode && !checkBoxes.get("m" + car.getId()).isSelected()) {
657                            // change to set button so user can remove car from train
658                            pMoves.add(addSet(car));
659                        } else {
660                            pMoves.add(checkBoxes.get("m" + car.getId()));
661                        }
662                    } else {
663                        String text;
664                        if (car.isUtility()) {
665                            text = trainCommon.setoutUtilityCars(carList, car, TrainCommon.LOCAL, isManifest);
666                            if (text == null) {
667                                continue; // this car type has already been processed
668                            }
669                        } else {
670                            text = trainCommon.localMoveCar(car, isManifest);
671                        }
672                        JCheckBox checkBox = new JCheckBox(text);
673                        setCheckBoxFont(checkBox);
674                        addCheckBoxAction(checkBox);
675                        pMoves.add(checkBox);
676                        checkBoxes.put("m" + car.getId(), checkBox);
677                    }
678                }
679            }
680            // if not sorting by track, we're done
681            if (!Setup.isSortByTrackNameEnabled()) {
682                break;
683            }
684        }
685        // pad the panels in case the horizontal scroll bar appears
686        pPickups.add(new JLabel(Space));
687        pSetouts.add(new JLabel(Space));
688        pMoves.add(new JLabel(Space));
689    }
690
691    // replace the car or engine checkbox and text with only the road and number and
692    // a Set button
693    protected JPanel addSet(RollingStock rs) {
694        JPanel pSet = new JPanel();
695        pSet.setLayout(new GridBagLayout());
696        JButton setButton = new JButton(Bundle.getMessage("Set"));
697        setButton.setToolTipText(Bundle.getMessage("SetButtonToolTip"));
698        setButton.setName(rs.getId());
699        setButton.addActionListener((ActionEvent e) -> {
700            if (Car.class.isInstance(rs)) {
701                carSetButtonActionPerfomed(e);
702            } else {
703                engineSetButtonActionPerfomed(e);
704            }
705        });
706        JLabel label = new JLabel(TrainCommon.padString(rs.toString(),
707                Control.max_len_string_attibute + Control.max_len_string_road_number));
708        setLabelFont(label);
709        addItem(pSet, label, 0, 0);
710        addItemLeft(pSet, setButton, 1, 0);
711        pSet.setAlignmentX(LEFT_ALIGNMENT);
712        return pSet;
713    }
714
715    protected void setCheckBoxFont(JCheckBox checkBox) {
716        if (Setup.isTabEnabled()) {
717            Font font = new Font(Setup.getFontName(), Font.PLAIN, checkBox.getFont().getSize());
718            checkBox.setFont(font);
719        }
720    }
721
722    protected void setLabelFont(JLabel label) {
723        if (Setup.isTabEnabled()) {
724            Font font = new Font(Setup.getFontName(), Font.PLAIN, label.getFont().getSize());
725            label.setFont(font);
726        }
727    }
728
729    protected void setModifyButtonText() {
730        if (isSetMode) {
731            modifyButton.setText(Bundle.getMessage("Done"));
732        } else {
733            modifyButton.setText(Bundle.getMessage("Modify"));
734        }
735    }
736
737    // returns one of two possible departure strings for a train
738    protected String getStatus(RouteLocation rl, boolean isManifest) {
739        if (rl == _train.getTrainTerminatesRouteLocation()) {
740            return MessageFormat.format(TrainManifestText.getStringTrainTerminates(),
741                    new Object[] { _train.getTrainTerminatesName() });
742        }
743        if (rl != _train.getCurrentRouteLocation() &&
744                _train.getExpectedArrivalTime(rl).equals(Train.ALREADY_SERVICED)) {
745            return MessageFormat.format(TrainSwitchListText.getStringTrainDone(), new Object[] { _train.getName() });
746        }
747        if (!_train.isBuilt() || rl == null) {
748            return _train.getStatus();
749        }
750        if (Setup.isPrintLoadsAndEmptiesEnabled()) {
751            int emptyCars = _train.getNumberEmptyCarsInTrain(rl);
752            String text;
753            if (isManifest) {
754                text = TrainManifestText.getStringTrainDepartsLoads();
755            } else {
756                text = TrainSwitchListText.getStringTrainDepartsLoads();
757            }
758            return MessageFormat.format(text,
759                    new Object[] { rl.getSplitName(), rl.getTrainDirectionString(),
760                            _train.getNumberCarsInTrain(rl) - emptyCars, emptyCars, _train.getTrainLength(rl),
761                            Setup.getLengthUnit().toLowerCase(), _train.getTrainWeight(rl),
762                            _train.getTrainTerminatesName(), _train.getName() });
763        } else {
764            String text;
765            if (isManifest) {
766                text = TrainManifestText.getStringTrainDepartsCars();
767            } else {
768                text = TrainSwitchListText.getStringTrainDepartsCars();
769            }
770            return MessageFormat.format(text,
771                    new Object[] { rl.getSplitName(), rl.getTrainDirectionString(),
772                            _train.getNumberCarsInTrain(rl), _train.getTrainLength(rl),
773                            Setup.getLengthUnit().toLowerCase(), _train.getTrainWeight(rl),
774                            _train.getTrainTerminatesName(), _train.getName() });
775        }
776    }
777
778    protected void removeCarFromList(Car car) {
779        checkBoxes.remove("p" + car.getId());
780        checkBoxes.remove("s" + car.getId());
781        checkBoxes.remove("m" + car.getId());
782        log.debug("Car ({}) removed from list", car.toString());
783        if (car.isUtility()) {
784            clearAndUpdate(); // need to recalculate number of utility cars
785        }
786    }
787
788    protected void clearAndUpdate() {
789        trainCommon.clearUtilityCarTypes(); // reset the utility car counts
790        checkBoxes.clear();
791        isSetMode = false;
792        update();
793    }
794
795    // to be overridden
796    protected abstract void update();
797
798    protected void removePropertyChangeListerners() {
799        rollingStock.stream().forEach((rs) -> {
800            rs.removePropertyChangeListener(this);
801        });
802        rollingStock.clear();
803    }
804
805    @Override
806    public void dispose() {
807        _train = null;
808        _location = null;
809    }
810
811    @Override
812    public void propertyChange(PropertyChangeEvent e) {
813        log.debug("Property change {} for: {} old: {} new: {}", e.getPropertyName(), e.getSource(), e.getOldValue(),
814                e.getNewValue()); // NOI18N
815    }
816
817    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CommonConductorYardmasterPanel.class);
818}