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