001package jmri.jmrit.operations.locations.tools;
002
003import java.awt.Dimension;
004import java.awt.GridBagLayout;
005import java.util.ArrayList;
006import java.util.List;
007
008import javax.swing.*;
009
010import jmri.InstanceManager;
011import jmri.jmrit.operations.OperationsFrame;
012import jmri.jmrit.operations.OperationsXml;
013import jmri.jmrit.operations.locations.*;
014import jmri.jmrit.operations.rollingstock.cars.*;
015import jmri.jmrit.operations.setup.Control;
016import jmri.jmrit.operations.setup.Setup;
017import jmri.util.swing.JmriJOptionPane;
018
019/**
020 * Frame to display which locations service certain car loads
021 *
022 * @author Dan Boudreau Copyright (C) 2014
023 */
024public class LocationsByCarLoadFrame extends OperationsFrame implements java.beans.PropertyChangeListener {
025
026    private final static String RECEIVE = "r";
027    private final static String SHIP = "s";
028    private final static String HYPHEN = "-";
029
030    LocationManager locationManager;
031
032    // checkboxes track id as the checkbox name
033    ArrayList<JCheckBox> trackCheckBoxList = new ArrayList<>();
034    JPanel locationCheckBoxes = new JPanel();
035
036    // panels
037    JPanel pLocations;
038
039    // major buttons
040    JButton clearButton = new JButton(Bundle.getMessage("ClearAll"));
041    JButton setButton = new JButton(Bundle.getMessage("SelectAll"));
042    JButton saveButton = new JButton(Bundle.getMessage("ButtonSave"));
043
044    // check boxes
045    JCheckBox loadAndTypeCheckBox = new JCheckBox(Bundle.getMessage("TypeAndLoad"));
046
047    // radio buttons
048    // text field
049    // combo boxes
050    JComboBox<String> typeComboBox = InstanceManager.getDefault(CarTypes.class).getComboBox();
051    JComboBox<String> loadComboBox = InstanceManager.getDefault(CarLoads.class).getComboBox(null);
052
053    // selected location
054    Location _location;
055
056    public LocationsByCarLoadFrame() {
057        super();
058    }
059
060    public void initComponents(Location location) {
061        this._location = location;
062        initComponents();
063    }
064
065    @Override
066    public void initComponents() {
067
068        // load managers
069        locationManager = InstanceManager.getDefault(LocationManager.class);
070
071        // general GUI config
072        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
073
074        // Set up the panels
075        JPanel pCarType = new JPanel();
076        pCarType.setLayout(new GridBagLayout());
077        pCarType.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Type")));
078
079        addItem(pCarType, typeComboBox, 0, 0);
080
081        JPanel pLoad = new JPanel();
082        pLoad.setLayout(new GridBagLayout());
083        pLoad.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Load")));
084
085        addItem(pLoad, loadComboBox, 0, 0);
086        addItem(pLoad, loadAndTypeCheckBox, 1, 0);
087
088        pLocations = new JPanel();
089        pLocations.setLayout(new GridBagLayout());
090        JScrollPane locationPane = new JScrollPane(pLocations);
091        locationPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
092        locationPane.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Locations")));
093        updateLoadComboBox();
094
095        JPanel pButtons = new JPanel();
096        pButtons.setLayout(new GridBagLayout());
097        pButtons.setBorder(BorderFactory.createTitledBorder(""));
098
099        addItem(pButtons, clearButton, 0, 0);
100        addItem(pButtons, setButton, 1, 0);
101        addItem(pButtons, saveButton, 2, 0);
102
103        getContentPane().add(pCarType);
104        getContentPane().add(pLoad);
105        getContentPane().add(locationPane);
106        getContentPane().add(pButtons);
107
108        // setup combo box
109        addComboBoxAction(typeComboBox);
110        addComboBoxAction(loadComboBox);
111
112        // setup buttons
113        addButtonAction(setButton);
114        addButtonAction(clearButton);
115        addButtonAction(saveButton);
116
117        // setup checkbox
118        addCheckBoxAction(loadAndTypeCheckBox);
119
120        locationManager.addPropertyChangeListener(this);
121        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
122        InstanceManager.getDefault(CarLoads.class).addPropertyChangeListener(this);
123
124        // build menu
125        JMenuBar menuBar = new JMenuBar();
126        JMenu toolMenu = new JMenu(Bundle.getMessage("MenuTools"));
127        toolMenu.add(new PrintLocationsByCarTypesAction(false));
128        toolMenu.add(new PrintLocationsByCarTypesAction(true));
129        menuBar.add(toolMenu);
130        setJMenuBar(menuBar);
131        addHelpMenu("package.jmri.jmrit.operations.Operations_ModifyLocationsByCarType", true); // NOI18N
132
133        setPreferredSize(null); // we need to resize this frame
134        pack();
135        setMinimumSize(new Dimension(Control.panelWidth300, Control.panelHeight250));
136        setSize(getWidth() + 25, getHeight()); // make a bit wider to eliminate scroll bar
137        if (_location != null) {
138            setTitle(Bundle.getMessage("TitleModifyLocationLoad"));
139        } else {
140            setTitle(Bundle.getMessage("TitleModifyLocationsLoad"));
141        }
142        setVisible(true);
143    }
144
145    @Override
146    public void comboBoxActionPerformed(java.awt.event.ActionEvent ae) {
147        log.debug("combo box action");
148        if (ae.getSource() == loadComboBox) {
149            log.debug("Load combobox change, selected load: ({})", loadComboBox.getSelectedItem());
150            if (loadComboBox.isEnabled() && loadComboBox.getSelectedItem() != null) {
151                updateLocations();
152            }
153        }
154        if (ae.getSource() == typeComboBox) {
155            updateLoadComboBox();
156        }
157    }
158
159    // Save, Delete, Add
160    @Override
161    public void buttonActionPerformed(java.awt.event.ActionEvent ae) {
162        if (ae.getSource() == saveButton) {
163            save();
164        }
165        if (ae.getSource() == setButton) {
166            selectCheckboxes(true);
167        }
168        if (ae.getSource() == clearButton) {
169            selectCheckboxes(false);
170        }
171    }
172
173    /**
174     * Update the car types that locations and tracks service. Note that the
175     * checkbox name is the id of the location or track.
176     */
177    private void save() {
178        log.debug("save");
179        OperationsXml.save();
180        if (Setup.isCloseWindowOnSaveEnabled()) {
181            dispose();
182        }
183    }
184
185    /**
186     * Update tracks at locations based on car type serviced, or car loads
187     * serviced. If car loads, disable any tracks that don't service the car
188     * type selected.
189     */
190    private void updateLocations() {
191        log.debug("update checkboxes");
192        removePropertyChangeLocations();
193        trackCheckBoxList.clear();
194        int x = 0;
195        pLocations.removeAll();
196        String type = (String) typeComboBox.getSelectedItem();
197        String load = (String) loadComboBox.getSelectedItem();
198        log.debug("Selected car type : ({}) load ({})", type, load);
199        // did the location get deleted?
200        if (_location != null && locationManager.getLocationByName(_location.getName()) == null) {
201            _location = null;
202        }
203        List<Location> locations = locationManager.getLocationsByNameList();
204        for (Location location : locations) {
205            // show only one location?
206            if (_location != null && _location != location) {
207                continue;
208            }
209            location.addPropertyChangeListener(this);
210            JLabel locationName = new JLabel(location.getName());
211            addItemLeft(pLocations, locationName, 0, x++);
212            List<Track> tracks = location.getTracksByNameList(null);
213            for (Track track : tracks) {
214                track.addPropertyChangeListener(this);
215                JCheckBox cb = new JCheckBox(track.getName());
216                cb.setName(track.getId() + HYPHEN + RECEIVE);
217                addCheckBoxAction(cb);
218                trackCheckBoxList.add(cb);
219                cb.setEnabled(track.isTypeNameAccepted(type));
220                cb.setSelected(track.isLoadNameAndCarTypeAccepted(load, type));
221                addItemLeft(pLocations, cb, 1, x++);
222                if (cb.isEnabled()) {
223                    cb.setToolTipText(Bundle.getMessage("TipTrackCarLoad", load));
224                } else {
225                    cb.setToolTipText(Bundle.getMessage("TipTrackNotThisType", type));
226                }
227            }
228            if (location.isStaging()) {
229                JLabel ships = new JLabel(location.getName() + " (" + Bundle.getMessage("Ships") + ")");
230                addItemLeft(pLocations, ships, 0, x++);
231                for (Track track : tracks) {
232                    JCheckBox cb = new JCheckBox(track.getName());
233                    cb.setName(track.getId() + HYPHEN + SHIP);
234                    addCheckBoxAction(cb);
235                    trackCheckBoxList.add(cb);
236                    cb.setEnabled(track.isTypeNameAccepted(type));
237                    cb.setSelected(track.isLoadNameAndCarTypeShipped(load, type));
238                    addItemLeft(pLocations, cb, 1, x++);
239                    if (cb.isEnabled()) {
240                        cb.setToolTipText(Bundle.getMessage("TipTrackCarShipsLoad", load));
241                    } else {
242                        cb.setToolTipText(Bundle.getMessage("TipTrackNotThisType", type));
243                    }
244                }
245
246            }
247        }
248        pLocations.revalidate();
249        repaint();
250    }
251
252    private void updateTypeComboBox() {
253        log.debug("update type combobox");
254        InstanceManager.getDefault(CarTypes.class).updateComboBox(typeComboBox);
255    }
256
257    private void updateLoadComboBox() {
258        log.debug("update load combobox");
259        if (typeComboBox.getSelectedItem() != null) {
260            String type = (String) typeComboBox.getSelectedItem();
261            String load = (String) loadComboBox.getSelectedItem();
262            log.debug("Selected car type : ({}) load ({})", type, load);
263            InstanceManager.getDefault(CarLoads.class).updateComboBox(type, loadComboBox);
264            loadComboBox.setEnabled(false); // used as a flag to prevent updateLocations()
265            if (load != null) {
266                loadComboBox.setSelectedItem(load);
267            }
268            loadComboBox.setEnabled(true);
269            updateLocations();
270        }
271    }
272
273    private void updateLoadStatusCheckBox(Track track) {
274        String type = (String) typeComboBox.getSelectedItem();
275        String load = (String) loadComboBox.getSelectedItem();
276        log.debug("Update load status for track ({}, {})", track.getLocation().getName(), track.getName());
277        for (JCheckBox cb : trackCheckBoxList) {
278            if (cb.getName().split(HYPHEN)[0].equals(track.getId())) {
279                if (cb.getName().split(HYPHEN)[1].equals(RECEIVE)) {
280                    cb.setSelected(track.isLoadNameAndCarTypeAccepted(load, type));
281                } else {
282                    cb.setSelected(track.isLoadNameAndCarTypeShipped(load, type));
283                }
284            }
285        }
286    }
287
288    private void selectCheckboxes(boolean select) {
289        for (JCheckBox cb : trackCheckBoxList) {
290            if (cb.isEnabled()) {
291                cb.setSelected(select);
292                checkBoxUpdate(cb);
293            }
294        }
295    }
296
297    TrackLoadEditFrame tlef; // if there's an issue bring up the load edit window
298
299    @Override
300    public void checkBoxActionPerformed(java.awt.event.ActionEvent ae) {
301        if (ae.getSource() == loadAndTypeCheckBox) {
302            updateLocations();
303            return;
304        }
305        JCheckBox cb = (JCheckBox) ae.getSource();
306        checkBoxUpdate(cb);
307    }
308
309    private void checkBoxUpdate(JCheckBox cb) {
310        String[] locId = cb.getName().split(HYPHEN);
311        Location loc = locationManager.getLocationById(locId[0].split(Location.LOC_TRACK_REGIX)[0]);
312        if (loc != null) {
313            Track track = loc.getTrackById(locId[0]);
314            // if enabled track services this car type
315            log.debug("CheckBox : {} track: ({}) isEnabled: {} isSelected: {}", cb.getName(), track.getName(), cb
316                    .isEnabled(), cb.isSelected());
317            if (cb.isEnabled()) {
318                boolean needLoadTrackEditFrame = false;
319                String loadName = (String) loadComboBox.getSelectedItem();
320                String load = loadName;
321                String type = (String) typeComboBox.getSelectedItem();
322                log.debug("Selected load ({})", loadName);
323                if (loadAndTypeCheckBox.isSelected()) {
324                    loadName = type + CarLoad.SPLIT_CHAR + loadName;
325                }
326                if (locId[1].equals(RECEIVE)) {
327                    if (cb.isSelected()) {
328                        if (track.getLoadOption().equals(Track.ALL_LOADS)) {
329                            log.debug("All loads selected for track ({})", track.getName());
330                        } else if (track.getLoadOption().equals(Track.INCLUDE_LOADS)) {
331                            track.addLoadName(loadName);
332                        } else if (track.getLoadOption().equals(Track.EXCLUDE_LOADS)) {
333                            track.deleteLoadName(loadName);
334                            // need to check if load configuration is to exclude all car types using this load
335                            if (!track.isLoadNameAccepted(load)) {
336                                JmriJOptionPane.showMessageDialog(this,
337                                        Bundle.getMessage("WarningExcludeTrackLoad", track.getLocation().getName(),
338                                                track.getName(), load),
339                                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
340                                needLoadTrackEditFrame = true;
341                            } else if (!track.isLoadNameAndCarTypeAccepted(load, type)) {
342                                JmriJOptionPane.showMessageDialog(this,
343                                        Bundle.getMessage("WarningExcludeTrackTypeAndLoad",
344                                                track.getLocation().getName(), track.getName(), type, load,
345                                                NEW_LINE + type + CarLoad.SPLIT_CHAR + load),
346                                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
347                                needLoadTrackEditFrame = true;
348                            }
349                        }
350                    } else {
351                        if (track.getLoadOption().equals(Track.INCLUDE_LOADS)) {
352                            track.deleteLoadName(loadName);
353                            // need to check if load configuration is to accept all car types using this load
354                            if (track.isLoadNameAccepted(load)) {
355                                JmriJOptionPane.showMessageDialog(this,
356                                        Bundle.getMessage("WarningAcceptTrackLoad", track.getLocation().getName(),
357                                                track.getName(), load),
358                                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
359                                needLoadTrackEditFrame = true;
360                            } else if (track.isLoadNameAndCarTypeAccepted(load, type)) {
361                                JmriJOptionPane.showMessageDialog(this,
362                                        Bundle.getMessage("WarningAcceptTrackTypeAndLoad",
363                                                track.getLocation().getName(), track.getName(), type, load,
364                                                NEW_LINE + type + CarLoad.SPLIT_CHAR + load),
365                                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
366                                needLoadTrackEditFrame = true;
367                            }
368                        } else if (track.getLoadOption().equals(Track.EXCLUDE_LOADS)) {
369                            track.addLoadName(loadName);
370                        } else if (track.getLoadOption().equals(Track.ALL_LOADS)) {
371                            // need to exclude this load
372                            track.setLoadOption(Track.EXCLUDE_LOADS);
373                            track.addLoadName(loadName);
374                        }
375                    }
376                }
377                if (locId[1].equals(SHIP)) {
378                    if (cb.isSelected()) {
379                        if (track.getShipLoadOption().equals(Track.ALL_LOADS)) {
380                            log.debug("Ship all loads selected for track ({})", track.getName());
381                        } else if (track.getShipLoadOption().equals(Track.INCLUDE_LOADS)) {
382                            track.addShipLoadName(loadName);
383                        } else if (track.getShipLoadOption().equals(Track.EXCLUDE_LOADS)) {
384                            track.deleteShipLoadName(loadName);
385                            // need to check if load configuration is to exclude all car types using this load
386                            if (!track.isLoadNameShipped(load)) {
387                                JmriJOptionPane.showMessageDialog(this,
388                                        Bundle.getMessage("WarningExcludeTrackShipLoad", track.getLocation().getName(),
389                                                track.getName(), load),
390                                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
391                                needLoadTrackEditFrame = true;
392                            } else if (!track.isLoadNameAndCarTypeShipped(load, type)) {
393                                JmriJOptionPane.showMessageDialog(this,
394                                        Bundle.getMessage("WarningExcludeTrackShipTypeAndLoad",
395                                                track.getLocation().getName(), track.getName(), type, load,
396                                                NEW_LINE + type + CarLoad.SPLIT_CHAR + load),
397                                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
398                                needLoadTrackEditFrame = true;
399                            }
400                        }
401                    } else {
402                        if (track.getShipLoadOption().equals(Track.INCLUDE_LOADS)) {
403                            track.deleteShipLoadName(loadName);
404                            // need to check if load configuration is to accept all car types using this load
405                            if (track.isLoadNameShipped(load)) {
406                                JmriJOptionPane.showMessageDialog(this,
407                                        Bundle.getMessage("WarningShipTrackLoad", track.getLocation().getName(),
408                                                track.getName(), load),
409                                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
410                                needLoadTrackEditFrame = true;
411                            } else if (track.isLoadNameAndCarTypeShipped(load, type)) {
412                                JmriJOptionPane.showMessageDialog(this,
413                                        Bundle.getMessage("WarningShipTrackTypeAndLoad", track.getLocation().getName(),
414                                                track.getName(), type, load,
415                                                NEW_LINE + type + CarLoad.SPLIT_CHAR + load),
416                                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
417                                needLoadTrackEditFrame = true;
418                            }
419                        } else if (track.getShipLoadOption().equals(Track.EXCLUDE_LOADS)) {
420                            track.addShipLoadName(loadName);
421                        } else if (track.getShipLoadOption().equals(Track.ALL_LOADS)) {
422                            // need to exclude this load
423                            track.setShipLoadOption(Track.EXCLUDE_LOADS);
424                            track.addShipLoadName(loadName);
425                        }
426                    }
427                }
428                if (needLoadTrackEditFrame) {
429                    if (tlef != null) {
430                        tlef.dispose();
431                    }
432                    tlef = new TrackLoadEditFrame();
433                    tlef.initComponents(track.getLocation(), track);
434                }
435            }
436        }
437    }
438
439    private void removePropertyChangeLocations() {
440        for (Location location : locationManager.getList()) {
441            location.removePropertyChangeListener(this);
442            List<Track> tracks = location.getTracksList();
443            for (Track track : tracks) {
444                track.removePropertyChangeListener(this);
445            }
446        }
447    }
448
449    @Override
450    public void dispose() {
451        locationManager.removePropertyChangeListener(this);
452        InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this);
453        InstanceManager.getDefault(CarLoads.class).removePropertyChangeListener(this);
454        removePropertyChangeLocations();
455        super.dispose();
456    }
457
458    @Override
459    public void propertyChange(java.beans.PropertyChangeEvent e) {
460        log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
461                .getNewValue());
462        if (e.getPropertyName().equals(LocationManager.LISTLENGTH_CHANGED_PROPERTY) ||
463                e.getPropertyName().equals(Location.TYPES_CHANGED_PROPERTY) ||
464                e.getPropertyName().equals(Location.NAME_CHANGED_PROPERTY) ||
465                e.getPropertyName().equals(Location.TRACK_LISTLENGTH_CHANGED_PROPERTY) ||
466                e.getPropertyName().equals(Track.TYPES_CHANGED_PROPERTY) ||
467                e.getPropertyName().equals(Track.NAME_CHANGED_PROPERTY)) {
468            updateLocations();
469        }
470        if (e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY) ||
471                e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY)) {
472            updateTypeComboBox();
473        }
474        if (e.getPropertyName().equals(CarLoads.LOAD_CHANGED_PROPERTY)) {
475            updateLoadComboBox();
476        }
477        if (e.getPropertyName().equals(Track.LOADS_CHANGED_PROPERTY) && e.getSource().getClass().equals(Track.class)) {
478            Track track = (Track) e.getSource();
479            updateLoadStatusCheckBox(track);
480        }
481    }
482
483    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LocationsByCarLoadFrame.class);
484}