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