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