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