001package jmri.jmrit.operations.trains.tools;
002
003import java.awt.Dimension;
004import java.awt.GridBagLayout;
005import java.util.List;
006
007import javax.swing.*;
008
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012import jmri.InstanceManager;
013import jmri.jmrit.operations.OperationsFrame;
014import jmri.jmrit.operations.locations.*;
015import jmri.jmrit.operations.locations.schedules.Schedule;
016import jmri.jmrit.operations.rollingstock.cars.*;
017import jmri.jmrit.operations.routes.Route;
018import jmri.jmrit.operations.routes.RouteLocation;
019import jmri.jmrit.operations.trains.Train;
020import jmri.jmrit.operations.trains.TrainManager;
021
022/**
023 * Frame to display by rolling stock, the locations serviced by this train
024 *
025 * @author Dan Boudreau Copyright (C) 2010, 2013, 2014
026 */
027public class TrainByCarTypeFrame extends OperationsFrame implements java.beans.PropertyChangeListener {
028
029    protected static final String POINTER = "    -->";
030    
031    // train
032    Train _train;
033
034    LocationManager locationManager = InstanceManager.getDefault(LocationManager.class);
035
036    // panels
037    JPanel pRoute = new JPanel();
038
039    // radio buttons
040    // combo boxes
041    JComboBox<Train> trainsComboBox = InstanceManager.getDefault(TrainManager.class).getTrainComboBox();
042    JComboBox<String> typeComboBox = InstanceManager.getDefault(CarTypes.class).getComboBox();
043    JComboBox<Car> carsComboBox = new JComboBox<>();
044
045    // The car currently selected
046    Car _car;
047
048    /**
049     * Show how cars for a train can be serviced
050     * 
051     * @param train the selected train
052     */
053    public TrainByCarTypeFrame(Train train) {
054        super();
055        _train = train;
056        initComponents(null);
057    }
058
059    /**
060     * Show how a car for a given train is serviced
061     * 
062     * @param car the car being checked
063     */
064    public TrainByCarTypeFrame(Car car) {
065        super();
066        if (car != null) {
067            _train = car.getTrain();
068        }
069        initComponents(car);
070    }
071
072    private void initComponents(Car car) {
073        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
074
075        // Set up the panels
076        JPanel pTrain = new JPanel();
077        pTrain.setLayout(new GridBagLayout());
078        pTrain.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Train")));
079        pTrain.setMaximumSize(new Dimension(2000, 50));
080
081        addItem(pTrain, trainsComboBox, 0, 0);
082        trainsComboBox.setSelectedItem(_train);
083
084        JPanel pCarType = new JPanel();
085        pCarType.setLayout(new GridBagLayout());
086        pCarType.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Type")));
087        pCarType.setMaximumSize(new Dimension(2000, 50));
088
089        addItem(pCarType, typeComboBox, 0, 0);
090        addItem(pCarType, carsComboBox, 1, 0);
091
092        // increase width of combobox so large text names display properly
093        Dimension boxsize = typeComboBox.getMinimumSize();
094        if (boxsize != null) {
095            boxsize.setSize(boxsize.width + 10, boxsize.height);
096            typeComboBox.setMinimumSize(boxsize);
097        }
098
099        adjustCarsComboBoxSize();
100
101        pRoute.setLayout(new GridBagLayout());
102        JScrollPane locationPane = new JScrollPane(pRoute);
103        locationPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
104        locationPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Route")));
105        updateCarsComboBox();
106        updateRoute();
107
108        getContentPane().add(pTrain);
109        getContentPane().add(pCarType);
110        getContentPane().add(locationPane);
111
112        // setup combo box
113        addComboBoxAction(trainsComboBox);
114        addComboBoxAction(typeComboBox);
115        addComboBoxAction(carsComboBox);
116
117        if (car != null) {
118            typeComboBox.setSelectedItem(car.getTypeName());
119            carsComboBox.setSelectedItem(car);
120        }
121
122        locationManager.addPropertyChangeListener(this);
123        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
124        // listen to all tracks and locations
125        addLocationAndTrackPropertyChange();
126
127        addHelpMenu("package.jmri.jmrit.operations.Operations_TrainShowCarTypesServiced", true); // NOI18N
128
129        setPreferredSize(null);
130        initMinimumSize();
131    }
132
133    @Override
134    public void comboBoxActionPerformed(java.awt.event.ActionEvent ae) {
135        log.debug("combo box action");
136        if (ae.getSource().equals(typeComboBox)) {
137            updateCarsComboBox();
138        }
139        updateRoute();
140        if (ae.getSource().equals(trainsComboBox)) {
141            pack();
142        }
143    }
144
145    private void updateRoute() {
146        if (_train != null) {
147            _train.removePropertyChangeListener(this);
148        }
149
150        pRoute.removeAll();
151
152        if (trainsComboBox.getSelectedItem() == null) {
153            _train = null;
154        } else {
155            _train = (Train) trainsComboBox.getSelectedItem();
156        }
157
158        if (_train == null) {
159            setTitle(Bundle.getMessage("MenuItemShowCarTypes"));
160            repaint();
161            return;
162        }
163
164        setTitle(Bundle.getMessage("MenuItemShowCarTypes") + " " + _train.getName());
165        _train.addPropertyChangeListener(this);
166        log.debug("update locations served by train ({})", _train.getName());
167
168        int y = 0;
169        String carType = (String) typeComboBox.getSelectedItem();
170        if (_car != null) {
171            _car.removePropertyChangeListener(this);
172        }
173        _car = null;
174        if (carsComboBox.getSelectedItem() != null) {
175            _car = (Car) carsComboBox.getSelectedItem();
176            _car.addPropertyChangeListener(this);
177        }
178        Route route = _train.getRoute();
179        if (route == null) {
180            repaint();
181            return;
182        }
183        List<RouteLocation> routeList = route.getLocationsBySequenceList();
184        for (RouteLocation rl : routeList) {
185            JLabel loc = new JLabel();
186            String locationName = rl.getName();
187            loc.setText(locationName);
188            addItemLeft(pRoute, loc, 0, y++);
189            Location location = locationManager.getLocationByName(locationName);
190            if (location == null) {
191                continue;
192            }
193            if (_car != null && _car.getTrack() != null && !_car.getTrack().isDestinationAccepted(location)) {
194                JLabel locText = new JLabel();
195                locText.setText(Bundle.getMessage("CarOnTrackDestinationRestriction",
196                        _car.toString(), _car.getLocationName(), _car.getTrackName(), locationName));
197                addItemWidth(pRoute, locText, 2, 1, y++);
198                if (_car.getLocation() != location)
199                    continue;
200            }
201            List<Track> tracks = location.getTracksByNameList(null);
202            for (Track track : tracks) {
203                // show the car's track if there's a track destination
204                // restriction
205                if (_car != null &&
206                        _car.getTrack() != null &&
207                        !_car.getTrack().isDestinationAccepted(location) &&
208                        _car.getTrack() != track) {
209                    continue;
210                }
211                JLabel trk = new JLabel();
212                trk.setText(track.getName());
213                addItemLeft(pRoute, trk, 1, y);
214                // is the car at this location and track?
215                if (_car != null && location.equals(_car.getLocation()) && track.equals(_car.getTrack())) {
216                    JLabel here = new JLabel(POINTER); // NOI18N
217                    addItemLeft(pRoute, here, 0, y);
218                }
219                JLabel op = new JLabel();
220                addItemLeft(pRoute, op, 2, y++);
221                if (!_train.isTypeNameAccepted(carType)) {
222                    op.setText(Bundle.getMessage("X(TrainType)"));
223                } else if (_car != null && !_train.isCarRoadNameAccepted(_car.getRoadName())) {
224                    op.setText(Bundle.getMessage("X(TrainRoad)"));
225                } // TODO need to do the same tests for caboose changes in the
226                  // train's route
227                else if (_car != null &&
228                        _car.isCaboose() &&
229                        _train.isCabooseNeeded() &&
230                        location.equals(_car.getLocation()) &&
231                        track.equals(_car.getTrack()) &&
232                        !_train.getCabooseRoad().equals(Train.NONE) &&
233                        !_car.getRoadName().equals(_train.getCabooseRoad()) &&
234                        location.getName().equals(_train.getTrainDepartsName())) {
235                    op.setText(Bundle.getMessage("X(TrainRoad)"));
236                } else if (_car != null &&
237                        _car.hasFred() &&
238                        _train.isFredNeeded() &&
239                        location.equals(_car.getLocation()) &&
240                        track.equals(_car.getTrack()) &&
241                        !_train.getCabooseRoad().equals(Train.NONE) &&
242                        !_car.getRoadName().equals(_train.getCabooseRoad()) &&
243                        location.getName().equals(_train.getTrainDepartsName())) {
244                    op.setText(Bundle.getMessage("X(TrainRoad)"));
245                } else if (_car != null &&
246                        !_car.isCaboose() &&
247                        !_car.isPassenger() &&
248                        !_train.isLoadNameAccepted(_car.getLoadName(), _car.getTypeName())) {
249                    op.setText(Bundle.getMessage("X(TrainLoad)"));
250                } else if (_car != null && !_train.isBuiltDateAccepted(_car.getBuilt())) {
251                    op.setText(Bundle.getMessage("X(TrainBuilt)"));
252                } else if (_car != null && !_train.isOwnerNameAccepted(_car.getOwnerName())) {
253                    op.setText(Bundle.getMessage("X(TrainOwner)"));
254                } else if (_train.isLocationSkipped(rl.getId())) {
255                    op.setText(Bundle.getMessage("X(TrainSkips)"));
256                } else if (!rl.isDropAllowed() && !rl.isPickUpAllowed()) {
257                    op.setText(Bundle.getMessage("X(Route)"));
258                } else if (rl.getMaxCarMoves() <= 0) {
259                    op.setText(Bundle.getMessage("X(RouteMoves)"));
260                } else if (!location.acceptsTypeName(carType)) {
261                    op.setText(Bundle.getMessage("X(LocationType)"));
262                } // check route before checking train, check train calls check
263                  // route
264                else if (!track.isPickupRouteAccepted(route) && !track.isDropRouteAccepted(route)) {
265                    op.setText(Bundle.getMessage("X(TrackRoute)"));
266                } else if (!track.isPickupTrainAccepted(_train) && !track.isDropTrainAccepted(_train)) {
267                    op.setText(Bundle.getMessage("X(TrackTrain)"));
268                } else if (!track.isTypeNameAccepted(carType)) {
269                    op.setText(Bundle.getMessage("X(TrackType)"));
270                } else if (_car != null && !track.isRoadNameAccepted(_car.getRoadName())) {
271                    op.setText(Bundle.getMessage("X(TrackRoad)"));
272                } else if (_car != null &&
273                        _car.getTrack() != track &&
274                        !track.isLoadNameAndCarTypeAccepted(_car.getLoadName(), _car.getTypeName())) {
275                    op.setText(Bundle.getMessage("X(TrackLoad)"));
276                } else if (_car != null &&
277                        !track.isDestinationAccepted(_car.getFinalDestination()) &&
278                        _car.getDestination() == null) {
279                    op.setText(Bundle.getMessage("X(TrackDestination)"));
280                } else if ((rl.getTrainDirection() & location.getTrainDirections()) == 0) {
281                    op.setText(Bundle.getMessage("X(DirLoc)"));
282                } else if ((rl.getTrainDirection() & track.getTrainDirections()) == 0) {
283                    op.setText(Bundle.getMessage("X(DirTrk)"));
284                } else if (!track.checkScheduleAttribute(Track.TYPE, carType, null)) {
285                    op.setText(Bundle.getMessage("X(ScheduleType)"));
286                } else if (!track.checkScheduleAttribute(Track.LOAD, carType, _car)) {
287                    op.setText(Bundle.getMessage("X(ScheduleLoad)"));
288                } else if (!track.checkScheduleAttribute(Track.ROAD, carType, _car)) {
289                    op.setText(Bundle.getMessage("X(ScheduleRoad)"));
290                } else if (!track.checkScheduleAttribute(Track.TRAIN_SCHEDULE, carType, _car)) {
291                    op.setText(Bundle.getMessage("X(ScheduleTrain)"));
292                } else if (!track.checkScheduleAttribute(Track.ALL, carType, _car)) {
293                    op.setText(Bundle.getMessage("X(Schedule)"));
294                } else if (!track.isPickupTrainAccepted(_train)) {
295                    // can the train drop off car?
296                    if (rl.isDropAllowed() && track.isDropTrainAccepted(_train)) {
297                        op.setText(Bundle.getMessage("DropOnly"));
298                    } else {
299                        op.setText(Bundle.getMessage("X(TrainPickup)"));
300                    }
301                } else if (!track.isDropTrainAccepted(_train)) {
302                    // can the train pick up car?
303                    if (rl.isPickUpAllowed() && track.isPickupTrainAccepted(_train)) {
304                        op.setText(Bundle.getMessage("PickupOnly"));
305                    } else {
306                        op.setText(Bundle.getMessage("X(TrainDrop)"));
307                    }
308                    // determine if a local move with default loads
309                } else if (!_train.isLocalSwitcher() &&
310                        _car != null &&
311                        _car.getTrack() != track &&
312                        _car.getLocation() == track.getLocation() &&
313                        _car.getFinalDestination() == null &&
314                        _car.getDivision() == null &&
315                        (_car.getLoadName().equals(InstanceManager.getDefault(CarLoads.class).getDefaultEmptyName()) ||
316                                _car.getLoadName()
317                                        .equals(InstanceManager.getDefault(CarLoads.class).getDefaultLoadName()))) {
318                    op.setText(Bundle.getMessage("X(LocalMove)"));
319                    // determine if local move with custom load or final
320                    // destination is allowed
321                } else if (!_train.isAllowLocalMovesEnabled() &&
322                        _car != null &&
323                        _car.getTrack() != track &&
324                        _car.getLocation() == track.getLocation() &&
325                        (_car.getDivision() != null ||
326                                _car.getFinalDestination() != null ||
327                                !_car.getLoadName()
328                                        .equals(InstanceManager.getDefault(CarLoads.class).getDefaultEmptyName()) &&
329                                        !_car.getLoadName().equals(
330                                                InstanceManager.getDefault(CarLoads.class).getDefaultLoadName()))) {
331                    op.setText(Bundle.getMessage("X(TrainLocalMove)"));
332                    // determine if spur can accept car with custom load
333                } else if (track.isSpur() &&
334                        track.getSchedule() == null &&
335                        _car != null &&
336                        _car.getTrack() != track &&
337                        !_car.getLoadName().equals(InstanceManager.getDefault(CarLoads.class).getDefaultEmptyName()) &&
338                        !_car.getLoadName().equals(InstanceManager.getDefault(CarLoads.class).getDefaultLoadName())) {
339                    op.setText(Bundle.getMessage("X(TrackCustomLoad)"));
340                    // empty car with a home division
341                } else if (_car != null &&
342                        _car.getDivision() != null &&
343                        _car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) &&
344                        _car.getDivision() != track.getDivision() &&
345                        !track.isInterchange()) {
346                    op.setText(Bundle.getMessage("X(Division)"));
347                    // loaded car with a home division
348                } else if (_car != null &&
349                        _car.getDivision() != null &&
350                        _car.getLoadType().equals(CarLoad.LOAD_TYPE_LOAD) &&
351                        _car.getDivision() != _car.getTrack().getDivision() &&
352                        _car.getDivision() != track.getDivision() &&
353                        !track.isInterchange()) {
354                    op.setText(Bundle.getMessage("X(Division)"));
355                } else if (rl.isDropAllowed() && rl.isPickUpAllowed()) {
356                    op.setText(Bundle.getMessage("ButtonOK"));
357                } else if (rl.isDropAllowed()) {
358                    op.setText(Bundle.getMessage("DropOnly"));
359                } else if (rl.isPickUpAllowed()) {
360                    op.setText(Bundle.getMessage("PickupOnly"));
361                } else {
362                    op.setText("X"); // default shouldn't occur
363                }
364            }
365        }
366        pRoute.revalidate();
367        repaint();
368    }
369
370    private void updateComboBox() {
371        log.debug("update combobox");
372        InstanceManager.getDefault(CarTypes.class).updateComboBox(typeComboBox);
373    }
374
375    private void updateCarsComboBox() {
376        log.debug("update car combobox");
377        carsComboBox.removeAllItems();
378        String carType = (String) typeComboBox.getSelectedItem();
379        // load car combobox
380        carsComboBox.addItem(null);
381        List<Car> cars = InstanceManager.getDefault(CarManager.class).getByTypeList(carType);
382        for (Car car : cars) {
383            carsComboBox.addItem(car);
384        }
385    }
386
387    private void adjustCarsComboBoxSize() {
388        List<Car> cars = InstanceManager.getDefault(CarManager.class).getList();
389        for (Car car : cars) {
390            carsComboBox.addItem(car);
391        }
392        Dimension boxsize = carsComboBox.getMinimumSize();
393        if (boxsize != null) {
394            boxsize.setSize(boxsize.width + 10, boxsize.height);
395            carsComboBox.setMinimumSize(boxsize);
396        }
397        carsComboBox.removeAllItems();
398    }
399
400    /**
401     * Add property listeners for locations and tracks
402     */
403    private void addLocationAndTrackPropertyChange() {
404        for (Location loc : locationManager.getList()) {
405            loc.addPropertyChangeListener(this);
406            for (Track track : loc.getTracksList()) {
407                track.addPropertyChangeListener(this);
408                Schedule schedule = track.getSchedule();
409                if (schedule != null) {
410                    schedule.addPropertyChangeListener(this);
411                }
412            }
413        }
414    }
415
416    /**
417     * Remove property listeners for locations and tracks
418     */
419    private void removeLocationAndTrackPropertyChange() {
420        for (Location loc : locationManager.getList()) {
421            loc.removePropertyChangeListener(this);
422            for (Track track : loc.getTracksList()) {
423                track.removePropertyChangeListener(this);
424                Schedule schedule = track.getSchedule();
425                if (schedule != null) {
426                    schedule.removePropertyChangeListener(this);
427                }
428            }
429        }
430    }
431
432    @Override
433    public void dispose() {
434        locationManager.removePropertyChangeListener(this);
435        InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this);
436        removeLocationAndTrackPropertyChange();
437        if (_train != null) {
438            _train.removePropertyChangeListener(this);
439        }
440        if (_car != null) {
441            _car.removePropertyChangeListener(this);
442        }
443        super.dispose();
444    }
445
446    @Override
447    public void propertyChange(java.beans.PropertyChangeEvent e) {
448        log.debug("Property change ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e.getNewValue()); // NOI18N
449        if (e.getSource().equals(_car) ||
450                e.getSource().equals(_train) ||
451                e.getSource().getClass().equals(Track.class) ||
452                e.getSource().getClass().equals(Location.class) ||
453                e.getSource().getClass().equals(Schedule.class) ||
454                e.getPropertyName().equals(LocationManager.LISTLENGTH_CHANGED_PROPERTY) ||
455                e.getPropertyName().equals(Route.LISTCHANGE_CHANGED_PROPERTY)) {
456            updateRoute();
457        }
458        if (e.getPropertyName().equals(Train.DISPOSE_CHANGED_PROPERTY)) {
459            dispose();
460        }
461        if (e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY) ||
462                e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY)) {
463            updateComboBox();
464        }
465        if (e.getPropertyName().equals(Location.LENGTH_CHANGED_PROPERTY)) {
466            // a track has been add or deleted update property listeners
467            removeLocationAndTrackPropertyChange();
468            addLocationAndTrackPropertyChange();
469        }
470    }
471
472    private final static Logger log = LoggerFactory.getLogger(TrainByCarTypeFrame.class);
473}