001package jmri.jmrit.operations.locations.schedules.tools;
002
003import java.awt.*;
004
005import javax.swing.*;
006
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import jmri.InstanceManager;
011import jmri.jmrit.operations.OperationsFrame;
012import jmri.jmrit.operations.locations.*;
013import jmri.jmrit.operations.locations.schedules.Schedule;
014import jmri.jmrit.operations.locations.schedules.ScheduleItem;
015import jmri.jmrit.operations.rollingstock.cars.CarLoads;
016import jmri.jmrit.operations.rollingstock.cars.CarTypes;
017import jmri.jmrit.operations.rollingstock.cars.tools.CarLoadEditFrameAction;
018import jmri.jmrit.operations.rollingstock.cars.tools.PrintCarLoadsAction;
019import jmri.jmrit.operations.setup.Control;
020
021/**
022 * Frame to display spurs with schedules and their loads
023 *
024 * @author Dan Boudreau Copyright (C) 2012, 2015, 2021, 2025, 2026
025 */
026public class SchedulesByLoadFrame extends OperationsFrame implements java.beans.PropertyChangeListener {
027
028    // managers'
029    LocationManager locationManager = InstanceManager.getDefault(LocationManager.class);
030    CarLoads carLoads = InstanceManager.getDefault(CarLoads.class);
031    CarTypes carTypes = InstanceManager.getDefault(CarTypes.class);
032
033    // combo box
034    JComboBox<String> typesComboBox = carTypes.getComboBox();
035    JComboBox<String> loadsComboBox = new JComboBox<>();
036
037    // panels
038    JPanel locationsPanel;
039
040    // checkbox
041    JCheckBox allLoadsCheckBox = new JCheckBox(Bundle.getMessage("allLoads"));
042    JCheckBox allTypesCheckBox = new JCheckBox(Bundle.getMessage("allTypes"));
043
044    public SchedulesByLoadFrame() {
045        super(Bundle.getMessage("MenuItemShowSchedulesByLoad"));
046
047        // the following code sets the frame's initial state
048        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
049
050        // load the panel
051        JPanel p1 = new JPanel();
052        p1.setMaximumSize(new Dimension(2000, 200));
053        p1.setLayout(new BoxLayout(p1, BoxLayout.X_AXIS));
054
055        JPanel type = new JPanel();
056        type.setLayout(new GridBagLayout());
057        type.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Type")));
058        addItem(type, typesComboBox, 0, 0);
059        addItem(type, allTypesCheckBox, 1, 0);
060
061        JPanel load = new JPanel();
062        load.setLayout(new GridBagLayout());
063        load.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Load")));
064        addItem(load, loadsComboBox, 0, 0);
065        addItem(load, allLoadsCheckBox, 1, 0);
066
067        p1.add(type);
068        p1.add(load);
069
070        locationsPanel = new JPanel();
071        locationsPanel.setLayout(new GridBagLayout());
072        JScrollPane locationsPane = new JScrollPane(locationsPanel);
073        locationsPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
074        locationsPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Location")));
075
076        getContentPane().add(p1);
077        getContentPane().add(locationsPane);
078
079        addComboBoxAction(typesComboBox);
080        addComboBoxAction(loadsComboBox);
081
082        addCheckBoxAction(allTypesCheckBox);
083        addCheckBoxAction(allLoadsCheckBox);
084
085        // property changes
086        locationManager.addPropertyChangeListener(this);
087        carTypes.addPropertyChangeListener(this);
088        carLoads.addPropertyChangeListener(this);
089
090        // build menu
091        JMenuBar menuBar = new JMenuBar();
092        JMenu toolMenu = new JMenu(Bundle.getMessage("MenuTools"));
093        toolMenu.add(new CarLoadEditFrameAction());
094        toolMenu.addSeparator();
095        toolMenu.add(new PrintCarLoadsAction(true));
096        toolMenu.add(new PrintCarLoadsAction(false));
097        toolMenu.addSeparator();
098        toolMenu.add(new PrintSchedulesByTypeAndLoadAction(true, this));
099        toolMenu.add(new PrintSchedulesByTypeAndLoadAction(false, this));
100        menuBar.add(toolMenu);
101        setJMenuBar(menuBar);
102        addHelpMenu("package.jmri.jmrit.operations.Operations_ShowSchedulesByCarTypeAndLoad", true); // NOI18N
103
104        // select first item to load contents
105        typesComboBox.setSelectedIndex(0);
106
107        initMinimumSize(new Dimension(Control.panelWidth1025, Control.panelHeight250));
108    }
109
110    @Override
111    public void comboBoxActionPerformed(java.awt.event.ActionEvent ae) {
112        if (ae.getSource() == typesComboBox) {
113            updateLoadComboBox();
114        }
115        if (ae.getSource() == loadsComboBox) {
116            updateLocations();
117        }
118
119    }
120
121    @Override
122    public void checkBoxActionPerformed(java.awt.event.ActionEvent ae) {
123        typesComboBox.setEnabled(!allTypesCheckBox.isSelected());
124        loadsComboBox.setEnabled(!allLoadsCheckBox.isSelected());
125        updateLoadComboBox();
126        updateLocations();
127    }
128
129    private void updateLoadComboBox() {
130        if (allTypesCheckBox.isSelected()) {
131            carLoads.updateComboBox(loadsComboBox);
132        } else if (typesComboBox.getSelectedItem() != null) {
133            String type = (String) typesComboBox.getSelectedItem();
134            carLoads.updateComboBox(type, loadsComboBox);
135        }
136    }
137
138    private void updateLocations() {
139        locationsPanel.removeAll();
140        String type = (String) typesComboBox.getSelectedItem();
141        String load = (String) loadsComboBox.getSelectedItem();
142        // determine if load is default empty or load
143        boolean defaultLoad = carLoads.getDefaultLoadName().equals(load) || carLoads.getDefaultEmptyName().equals(load);
144        log.debug("Update locations for type ({}) load ({})", type, load);
145
146        // build header
147        int row = 0;
148        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Spur")), 1, row);
149        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Schedule")), 2, row);
150        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Id")), 3, row);
151        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("ScheduleMode")), 4, row);
152        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Type")), 5, row);
153        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Random")), 6, row);
154        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Delivery")), 7, row);
155        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Road")), 8, row);
156        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Receive")), 9, row);
157        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Ship")), 10, row);
158        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Destination")), 11, row);
159        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Track")), 12, row);
160        addItemLeft(locationsPanel, new JLabel(Bundle.getMessage("Pickup")), 13, row++);
161
162        // now load data
163        for (Location location : locationManager.getLocationsByNameList()) {
164            // only spurs have schedules
165            if (!location.hasSchedules())
166                continue;
167            addItemLeft(locationsPanel, new JLabel(location.getName()), 0, row++);
168            // now look for a spur with a schedule
169            for (Track spur : location.getTracksByNameList(Track.SPUR)) {
170                Schedule sch = spur.getSchedule();
171                if (sch == null) {
172                    continue;
173                }
174                // listen for changes
175                spur.removePropertyChangeListener(this);
176                spur.addPropertyChangeListener(this);
177                sch.removePropertyChangeListener(this);
178                sch.addPropertyChangeListener(this);
179
180                // determine if schedule is requesting car type and load
181                for (ScheduleItem si : sch.getItemsBySequenceList()) {
182                    // skip if car type doesn't carry load name
183                    if (allTypesCheckBox.isSelected() &&
184                            !allLoadsCheckBox.isSelected() &&
185                            !carLoads.containsName(si.getTypeName(), load)) {
186                        continue;
187                    }
188                    if ((allTypesCheckBox.isSelected() || si.getTypeName().equals(type)) &&
189                            (allLoadsCheckBox.isSelected() ||
190                                    si.getReceiveLoadName().equals(load) ||
191                                    si.getReceiveLoadName().equals(ScheduleItem.NONE) ||
192                                    si.getShipLoadName().equals(load) ||
193                                    (si.getShipLoadName().equals(ScheduleItem.NONE) && defaultLoad))) {
194                        // is the schedule item valid?
195                        String status = spur.checkScheduleValid();
196                        if (!status.equals(Schedule.SCHEDULE_OKAY)) {
197                            JLabel errorSchedule = new JLabel("  " + status);
198                            errorSchedule.setForeground(Color.RED);
199                            addItemLeft(locationsPanel, errorSchedule, 0, row);
200                        }
201                        addItemLeft(locationsPanel, new JLabel(spur.getName()), 1, row);
202                        addItemLeft(locationsPanel, new JLabel(spur.getScheduleName()), 2, row);
203                        addItemLeft(locationsPanel, new JLabel(si.getId()), 3, row);
204                        addItemLeft(locationsPanel, new JLabel(spur.getScheduleModeName()), 4, row);
205                        addItemLeft(locationsPanel, new JLabel(si.getTypeName()), 5, row);
206                        addItemLeft(locationsPanel, new JLabel(si.getRandom()), 6, row);
207                        addItemLeft(locationsPanel, new JLabel(si.getSetoutTrainScheduleName()), 7, row);
208                        addItemLeft(locationsPanel, new JLabel(si.getRoadName()), 8, row);
209                        // report if spur can't service the selected load
210                        if (!allLoadsCheckBox.isSelected() &&
211                                si.getReceiveLoadName().equals(ScheduleItem.NONE) &&
212                                !spur.isLoadNameAndCarTypeAccepted(load, type)) {
213                            JLabel warnLoad =
214                                    new JLabel(Bundle.getMessage("spurNotTypeLoad", spur.getName(), type, load));
215                            warnLoad.setForeground(Color.BLUE);
216                            addItemLeft(locationsPanel, warnLoad, 9, row);
217                        } else {
218                            addItemLeft(locationsPanel, new JLabel(si.getReceiveLoadName()), 9, row);
219                        }
220                        addItemLeft(locationsPanel, new JLabel(si.getShipLoadName()), 10, row);
221                        // now the destination and track
222                        if (si.getDestination() != null) {
223                            addItemLeft(locationsPanel,
224                                    new JLabel(si.getDestinationName()), 11, row);
225                            addItemLeft(locationsPanel,
226                                    new JLabel(si.getDestinationTrackName()), 12, row);
227                        }
228                        addItemLeft(locationsPanel, new JLabel(si.getPickupTrainScheduleName()), 13, row++);
229                    }
230                }
231            }
232        }
233        locationsPanel.revalidate();
234        revalidate();
235        repaint();
236    }
237
238    public String getCarType() {
239        if (allTypesCheckBox.isSelected()) {
240            return null;
241        }
242        return (String) typesComboBox.getSelectedItem();
243    }
244
245    public String getCarLoad() {
246        if (allLoadsCheckBox.isSelected()) {
247            return null;
248        }
249        return (String) loadsComboBox.getSelectedItem();
250    }
251
252    @Override
253    public void dispose() {
254        locationManager.removePropertyChangeListener(this);
255        carTypes.removePropertyChangeListener(this);
256        carLoads.removePropertyChangeListener(this);
257        for (Track spur : locationManager.getTracks(Track.SPUR)) {
258            Schedule sch = spur.getSchedule();
259            if (sch == null) {
260                continue;
261            }
262            spur.removePropertyChangeListener(this);
263            sch.removePropertyChangeListener(this);
264        }
265        super.dispose();
266    }
267
268    @Override
269    public void propertyChange(java.beans.PropertyChangeEvent e) {
270        log.debug("Property change ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e.getNewValue()); // NOI18N
271
272        if (e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY)) {
273            carTypes.updateComboBox(typesComboBox);
274        }
275        if (e.getSource().getClass().equals(CarLoads.class)) {
276            carLoads.updateComboBox((String) typesComboBox.getSelectedItem(), loadsComboBox);
277        }
278        if (e.getSource().getClass().equals(Schedule.class) ||
279                e.getSource().getClass().equals(LocationManager.class) ||
280                e.getPropertyName().equals(Track.LOADS_CHANGED_PROPERTY) ||
281                e.getPropertyName().equals(Track.ROADS_CHANGED_PROPERTY) ||
282                e.getPropertyName().equals(Track.TYPES_CHANGED_PROPERTY)) {
283            updateLocations();
284        }
285    }
286
287    private static final Logger log = LoggerFactory.getLogger(SchedulesByLoadFrame.class);
288
289}