001package jmri.jmrit.operations.locations;
002
003import java.awt.Color;
004import java.awt.Dimension;
005import java.awt.event.ActionEvent;
006import java.text.MessageFormat;
007import java.util.ArrayList;
008import java.util.List;
009
010import javax.swing.*;
011
012import org.slf4j.Logger;
013import org.slf4j.LoggerFactory;
014
015import jmri.InstanceManager;
016import jmri.jmrit.operations.CommonConductorYardmasterPanel;
017import jmri.jmrit.operations.rollingstock.RollingStock;
018import jmri.jmrit.operations.rollingstock.cars.*;
019import jmri.jmrit.operations.rollingstock.engines.Engine;
020import jmri.jmrit.operations.routes.RouteLocation;
021import jmri.jmrit.operations.setup.Control;
022import jmri.jmrit.operations.setup.Setup;
023import jmri.jmrit.operations.trains.*;
024
025/**
026 * Yardmaster frame by track. Shows work at one location listed by track.
027 *
028 * @author Dan Boudreau Copyright (C) 2015
029 *
030 */
031public class YardmasterByTrackPanel extends CommonConductorYardmasterPanel {
032
033    protected Track _track = null;
034
035    // text panes
036    JTextPane textTrackCommentPane = new JTextPane();
037
038    // combo boxes
039    JComboBox<Track> trackComboBox = new JComboBox<>();
040
041    // buttons
042    JButton nextButton = new JButton(Bundle.getMessage("Next"));
043
044    // panel
045    JPanel pTrack = new JPanel();
046    JScrollPane pTrackPane;
047
048    public YardmasterByTrackPanel() {
049        this(null);
050    }
051
052    public YardmasterByTrackPanel(Location location) {
053        super();
054
055        // this window doesn't use the set button
056        modifyButton.setVisible(false);
057
058        _location = location;
059
060        textTrackCommentPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TrackComment")));
061        textTrackCommentPane.setBackground(null);
062        textTrackCommentPane.setEditable(false);
063        textTrackCommentPane.setMaximumSize(new Dimension(1000, 200));
064
065        JPanel pTrackSelect = new JPanel();
066        pTrackSelect.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Track")));
067        pTrackSelect.add(trackComboBox);
068        // add next button for web server
069        pTrackSelect.add(nextButton);
070
071        // work at this location by track
072        pTrack.setLayout(new BoxLayout(pTrack, BoxLayout.Y_AXIS));
073        pTrackPane = new JScrollPane(pTrack);
074
075        pLocationName.setMaximumSize(new Dimension(2000, 200));
076        pTrackSelect.setMaximumSize(new Dimension(2000, 200));
077        pButtons.setMaximumSize(new Dimension(2000, 200));
078
079        add(pLocationName);
080        add(textLocationCommentPane);
081        add(textSwitchListCommentPane);
082        add(pTrackSelect);
083        add(textTrackCommentPane);
084        add(pTrackComments);
085        add(pTrackPane);
086        add(pButtons);
087
088        if (_location != null) {
089            textLocationName.setText(_location.getName());
090            loadLocationComment(_location);
091            loadLocationSwitchListComment(_location);
092            updateTrackComboBox();
093            _location.addPropertyChangeListener(this);
094        }
095
096        update();
097
098        addComboBoxAction(trackComboBox);
099        addButtonAction(nextButton);
100    }
101
102    // Select, Clear, and Next Buttons
103    @Override
104    public void buttonActionPerformed(ActionEvent ae) {
105        if (ae.getSource() == nextButton) {
106            nextButtonAction();
107        }
108        super.buttonActionPerformed(ae);
109    }
110
111    private void nextButtonAction() {
112        log.debug("next button activated");
113        if (trackComboBox.getItemCount() > 1) {
114            int index = trackComboBox.getSelectedIndex();
115            // index = -1 if first item (null) in trainComboBox
116            if (index == -1) {
117                index = 1;
118            } else {
119                index++;
120            }
121            if (index >= trackComboBox.getItemCount()) {
122                index = 0;
123            }
124            trackComboBox.setSelectedIndex(index);
125        }
126    }
127
128    @Override
129    protected void comboBoxActionPerformed(ActionEvent ae) {
130        // made the combo box not visible during updates, so ignore if not visible
131        if (ae.getSource() == trackComboBox && trackComboBox.isVisible()) {
132            _track = null;
133            if (trackComboBox.getSelectedItem() != null) {
134                _track = (Track) trackComboBox.getSelectedItem();
135            }
136            update();
137        }
138    }
139
140    @Override
141    protected void update() {
142        // use invokeLater to prevent deadlock
143        SwingUtilities.invokeLater(() -> {
144            runUpdate();
145        });
146    }
147
148    private void runUpdate() {
149        log.debug("run update");
150        removePropertyChangeListerners();
151        trainCommon.clearUtilityCarTypes(); // reset the utility car counts
152        checkBoxes.clear();
153        pTrack.removeAll();
154        if (_track != null) {
155            pTrackPane.setBorder(BorderFactory.createTitledBorder(_track.getName()));
156            textTrackCommentPane.setText(TrainCommon.getTextColorString(_track.getComment()));
157            textTrackCommentPane.setForeground(TrainCommon.getTextColor(_track.getComment()));
158            textTrackCommentPane.setVisible(!_track.getComment().equals(Track.NONE));
159            for (Train train : trainManager.getTrainsArrivingThisLocationList(_track.getLocation())) {
160                JPanel pTrain = new JPanel();
161                pTrain.setLayout(new BoxLayout(pTrain, BoxLayout.Y_AXIS));
162                pTrain.setBorder(BorderFactory
163                        .createTitledBorder(MessageFormat.format(TrainSwitchListText.getStringScheduledWork(),
164                                new Object[] { train.getName(), train.getDescription() })));
165                // Track work comments
166                boolean pickupCar = false;
167                boolean setoutCar = false;
168                JTextPane textTrackCommentWorkPane = getTrackWorkCommentPane();
169                pTrain.add(textTrackCommentWorkPane);
170
171                boolean localCar = false;
172
173                // Engine
174                boolean pickupEngine = false;
175                boolean setoutEngine = false;
176
177                // pick ups
178                JPanel pPickups = new JPanel();
179                pPickups.setLayout(new BoxLayout(pPickups, BoxLayout.Y_AXIS));
180                pPickups.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Pickup")));
181                pPickups.setMaximumSize(new Dimension(2000, 2000));
182
183                // set outs
184                JPanel pSetouts = new JPanel();
185                pSetouts.setLayout(new BoxLayout(pSetouts, BoxLayout.Y_AXIS));
186                pSetouts.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("SetOut")));
187                pSetouts.setMaximumSize(new Dimension(2000, 2000));
188
189                // local moves
190                JPanel pLocal = new JPanel();
191                pLocal.setLayout(new BoxLayout(pLocal, BoxLayout.Y_AXIS));
192                pLocal.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("LocalMoves")));
193                pLocal.setMaximumSize(new Dimension(2000, 2000));
194
195                // List locos first
196                List<Engine> engList = engManager.getByTrainBlockingList(train);
197                if (Setup.isPrintHeadersEnabled()) {
198                    for (Engine engine : engList) {
199                        if (engine.getTrack() == _track) {
200                            JLabel header = new JLabel(Tab + trainCommon.getPickupEngineHeader());
201                            setLabelFont(header);
202                            pPickups.add(header);
203                            break;
204                        }
205                    }
206                }
207                for (Engine engine : engList) {
208                    if (engine.getTrack() == _track) {
209                        engine.addPropertyChangeListener(this);
210                        rollingStock.add(engine);
211                        JCheckBox checkBox = new JCheckBox(trainCommon.pickupEngine(engine));
212                        setCheckBoxFont(checkBox, Setup.getPickupEngineColor());
213                        pPickups.add(checkBox);
214                        pickupEngine = true;
215                        checkBoxes.put(engine.getId() + "p", checkBox);
216                        pTrack.add(pTrain);
217                    }
218                }
219                // now do locomotive set outs
220                if (Setup.isPrintHeadersEnabled()) {
221                    for (Engine engine : engList) {
222                        if (engine.getDestinationTrack() == _track) {
223                            JLabel header = new JLabel(Tab + trainCommon.getDropEngineHeader());
224                            setLabelFont(header);
225                            pSetouts.add(header);
226                            break;
227                        }
228                    }
229                }
230                for (Engine engine : engList) {
231                    if (engine.getDestinationTrack() == _track) {
232                        engine.addPropertyChangeListener(this);
233                        rollingStock.add(engine);
234                        JCheckBox checkBox = new JCheckBox(trainCommon.dropEngine(engine));
235                        setCheckBoxFont(checkBox, Setup.getDropEngineColor());
236                        pSetouts.add(checkBox);
237                        setoutEngine = true;
238                        checkBoxes.put(engine.getId(), checkBox);
239                        pTrack.add(pTrain);
240                    }
241                }
242                // now cars
243                List<Car> carList = carManager.getByTrainDestinationList(train);
244                if (Setup.isPrintHeadersEnabled()) {
245                    for (Car car : carList) {
246                        if (car.getTrack() == _track && car.getRouteDestination() != car.getRouteLocation()) {
247                            JLabel header = new JLabel(Tab +
248                                    trainCommon.getPickupCarHeader(!IS_MANIFEST, !TrainCommon.IS_TWO_COLUMN_TRACK));
249                            setLabelFont(header);
250                            pPickups.add(header);
251                            break;
252                        }
253                    }
254                }
255                // sort car pick ups by their destination
256                List<RouteLocation> routeList = train.getRoute().getLocationsBySequenceList();
257                for (RouteLocation rl : routeList) {
258                    for (Car car : carList) {
259                        if (car.getTrack() == _track &&
260                                car.getRouteDestination() != car.getRouteLocation() &&
261                                car.getRouteDestination() == rl) {
262                            car.addPropertyChangeListener(this);
263                            rollingStock.add(car);
264                            String text;
265                            if (car.isUtility()) {
266                                text = trainCommon.pickupUtilityCars(carList, car, !IS_MANIFEST,
267                                        !TrainCommon.IS_TWO_COLUMN_TRACK);
268                                if (text == null) {
269                                    continue; // this car type has already been processed
270                                }
271                            } else {
272                                text = trainCommon.pickupCar(car, !IS_MANIFEST, !TrainCommon.IS_TWO_COLUMN_TRACK);
273                            }
274                            pickupCar = true;
275                            JCheckBox checkBox = new JCheckBox(text);
276                            setCheckBoxFont(checkBox, Setup.getPickupColor());
277                            pPickups.add(checkBox);
278                            checkBoxes.put(car.getId() + "p", checkBox);
279                            pTrack.add(pTrain);
280                        }
281                    }
282                }
283                // now do car set outs
284                if (Setup.isPrintHeadersEnabled()) {
285                    for (Car car : carList) {
286                        if (car.getDestinationTrack() == _track &&
287                                car.getRouteDestination() != car.getRouteLocation()) {
288                            JLabel header = new JLabel(
289                                    Tab + trainCommon.getDropCarHeader(!IS_MANIFEST, !TrainCommon.IS_TWO_COLUMN_TRACK));
290                            setLabelFont(header);
291                            pSetouts.add(header);
292                            break;
293                        }
294                    }
295                }
296                for (Car car : carList) {
297                    if (car.getDestinationTrack() == _track && car.getRouteLocation() != car.getRouteDestination()) {
298                        car.addPropertyChangeListener(this);
299                        rollingStock.add(car);
300                        String text;
301                        if (car.isUtility()) {
302                            text = trainCommon.setoutUtilityCars(carList, car, !TrainCommon.LOCAL, !IS_MANIFEST);
303                            if (text == null) {
304                                continue; // this car type has already been processed
305                            }
306                        } else {
307                            text = trainCommon.dropCar(car, !IS_MANIFEST, !TrainCommon.IS_TWO_COLUMN_TRACK);
308                        }
309                        setoutCar = true;
310                        JCheckBox checkBox = new JCheckBox(text);
311                        setCheckBoxFont(checkBox, Setup.getDropColor());
312                        pSetouts.add(checkBox);
313                        checkBoxes.put(car.getId(), checkBox);
314                        pTrack.add(pTrain);
315                    }
316                }
317                // now do local car moves
318                if (Setup.isPrintHeadersEnabled()) {
319                    for (Car car : carList) {
320                        if ((car.getTrack() == _track || car.getDestinationTrack() == _track) &&
321                                car.getRouteDestination() == car.getRouteLocation()) {
322                            JLabel header = new JLabel(Tab + trainCommon.getLocalMoveHeader(!IS_MANIFEST));
323                            setLabelFont(header);
324                            pLocal.add(header);
325                            break;
326                        }
327                    }
328                }
329                for (Car car : carList) {
330                    if ((car.getTrack() == _track || car.getDestinationTrack() == _track) &&
331                            car.getRouteLocation() != null &&
332                            car.getRouteLocation() == car.getRouteDestination()) {
333                        car.addPropertyChangeListener(this);
334                        rollingStock.add(car);
335                        String text;
336                        if (car.isUtility()) {
337                            text = trainCommon.setoutUtilityCars(carList, car, TrainCommon.LOCAL, !IS_MANIFEST);
338                            if (text == null) {
339                                continue; // this car type has already been processed
340                            }
341                        } else {
342                            text = trainCommon.localMoveCar(car, !IS_MANIFEST);
343                        }
344                        if (car.getTrack() == _track) {
345                            pickupCar = true;
346                        }
347                        if (car.getDestinationTrack() == _track) {
348                            setoutCar = true;
349                        }
350                        JCheckBox checkBox = new JCheckBox(text);
351                        setCheckBoxFont(checkBox, Setup.getLocalColor());
352                        pLocal.add(checkBox);
353                        localCar = true;
354                        checkBoxes.put(car.getId(), checkBox);
355                        pTrack.add(pTrain);
356                    }
357                }
358                if (pickupCar && !setoutCar) {
359                    textTrackCommentWorkPane.setText(_track.getCommentPickup());
360                    textTrackCommentWorkPane
361                            .setForeground(TrainCommon.getTextColor(_track.getCommentPickupWithColor()));
362                } else if (!pickupCar && setoutCar) {
363                    textTrackCommentWorkPane.setText(_track.getCommentSetout());
364                    textTrackCommentWorkPane
365                            .setForeground(TrainCommon.getTextColor(_track.getCommentSetoutWithColor()));
366                } else if (pickupCar && setoutCar) {
367                    textTrackCommentWorkPane.setText(_track.getCommentBoth());
368                    textTrackCommentWorkPane.setForeground(TrainCommon.getTextColor(_track.getCommentBothWithColor()));
369                }
370                textTrackCommentWorkPane.setVisible(!textTrackCommentWorkPane.getText().isEmpty());
371
372                // only show panels that have work
373                if (pickupCar || pickupEngine) {
374                    pTrain.add(pPickups);
375                }
376                if (setoutCar || setoutEngine) {
377                    pTrain.add(pSetouts);
378                }
379                if (localCar) {
380                    pTrain.add(pLocal);
381                }
382
383                pTrackPane.validate();
384                pTrain.setMaximumSize(new Dimension(2000, pTrain.getHeight()));
385                pTrain.revalidate();
386            }
387            // now do car holds
388            // we only need the cars on this track
389            List<Car> rsList = carManager.getByTrainList();
390            List<Car> carList = new ArrayList<Car>();
391            for (Car rs : rsList) {
392                if (rs.getTrack() != _track || rs.getRouteLocation() != null)
393                    continue;
394                carList.add(rs);
395            }
396            JPanel pHoldCars = new JPanel();
397            pHoldCars.setLayout(new BoxLayout(pHoldCars, BoxLayout.Y_AXIS));
398            pHoldCars.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("HoldCars")));
399            for (Car car : carList) {
400                String text;
401                if (car.isUtility()) {
402                    String s = trainCommon.pickupUtilityCars(carList, car, !IS_MANIFEST,
403                            !TrainCommon.IS_TWO_COLUMN_TRACK);
404                    if (s == null)
405                        continue;
406                    text = TrainSwitchListText.getStringHoldCar().split("\\{")[0] + s.trim();
407                } else {
408                    text = MessageFormat.format(TrainSwitchListText.getStringHoldCar(),
409                            new Object[] {
410                                    TrainCommon.padAndTruncateIfNeeded(car.getRoadName(),
411                                            InstanceManager.getDefault(CarRoads.class).getMaxNameLength()),
412                                    TrainCommon.padAndTruncateIfNeeded(TrainCommon.splitString(car.getNumber()),
413                                            Control.max_len_string_print_road_number),
414                                    TrainCommon.padAndTruncateIfNeeded(car.getTypeName().split(TrainCommon.HYPHEN)[0],
415                                            InstanceManager.getDefault(CarTypes.class).getMaxNameLength()),
416                                    TrainCommon.padAndTruncateIfNeeded(car.getLength() + Setup.getLengthUnitAbv(),
417                                            Control.max_len_string_length_name),
418                                    TrainCommon.padAndTruncateIfNeeded(car.getLoadName(),
419                                            InstanceManager.getDefault(CarLoads.class).getMaxNameLength()),
420                                    TrainCommon.padAndTruncateIfNeeded(_track.getName(),
421                                            InstanceManager.getDefault(LocationManager.class).getMaxTrackNameLength()),
422                                    TrainCommon.padAndTruncateIfNeeded(car.getColor(),
423                                            InstanceManager.getDefault(CarColors.class).getMaxNameLength()) });
424
425                }
426                JCheckBox checkBox = new JCheckBox(text);
427                setCheckBoxFont(checkBox, Color.black);
428                pHoldCars.add(checkBox);
429                checkBoxes.put(car.getId(), checkBox);
430                pTrack.add(pHoldCars);
431            }
432            pTrackPane.validate();
433            pHoldCars.setMaximumSize(new Dimension(2000, pHoldCars.getHeight()));
434            pHoldCars.revalidate();
435        } else {
436            pTrackPane.setBorder(BorderFactory.createTitledBorder(""));
437            textTrackCommentPane.setVisible(false);
438        }
439    }
440
441    private JTextPane getTrackWorkCommentPane() {
442        JTextPane textTrackCommentWorkPane = new JTextPane();
443        textTrackCommentWorkPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Comment")));
444        textTrackCommentWorkPane.setBackground(null);
445        textTrackCommentWorkPane.setEditable(false);
446        return textTrackCommentWorkPane;
447    }
448
449    private void updateTrackComboBox() {
450        Object selectedItem = trackComboBox.getSelectedItem();
451        trackComboBox.setVisible(false); // used as a flag to ignore updates
452        if (_location != null) {
453            _location.updateComboBox(trackComboBox);
454        }
455        if (selectedItem != null) {
456            trackComboBox.setSelectedItem(selectedItem);
457        }
458        trackComboBox.setVisible(true);
459    }
460
461    @Override
462    public void dispose() {
463        if (_location != null)
464            _location.removePropertyChangeListener(this);
465        removePropertyChangeListerners();
466    }
467
468    @Override
469    public void propertyChange(java.beans.PropertyChangeEvent e) {
470        if (Control.SHOW_PROPERTY) {
471            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(),
472                    e.getNewValue());
473        }
474        if (e.getPropertyName().equals(RollingStock.ROUTE_LOCATION_CHANGED_PROPERTY)) {
475            update();
476        }
477        if (e.getPropertyName().equals(Location.TRACK_LISTLENGTH_CHANGED_PROPERTY)) {
478            updateTrackComboBox();
479        }
480    }
481
482    private final static Logger log = LoggerFactory.getLogger(YardmasterByTrackPanel.class);
483}