001package jmri.jmrit.operations.locations.schedules;
002
003import java.awt.Dimension;
004import java.awt.GridBagLayout;
005
006import javax.swing.*;
007
008import jmri.InstanceManager;
009import jmri.jmrit.operations.*;
010import jmri.jmrit.operations.locations.*;
011import jmri.jmrit.operations.locations.schedules.tools.*;
012import jmri.jmrit.operations.locations.tools.PrintLocationsAction;
013import jmri.jmrit.operations.rollingstock.cars.CarTypes;
014import jmri.jmrit.operations.setup.Control;
015import jmri.jmrit.operations.setup.Setup;
016import jmri.swing.JTablePersistenceManager;
017import jmri.util.swing.JmriJOptionPane;
018
019/**
020 * Frame for user edit of a schedule
021 *
022 * @author Dan Boudreau Copyright (C) 2008, 2011
023 */
024public class ScheduleEditFrame extends OperationsFrame implements java.beans.PropertyChangeListener {
025
026    ScheduleTableModel scheduleModel = new ScheduleTableModel();
027    JTable scheduleTable = new JTable(scheduleModel);
028    JScrollPane schedulePane;
029
030    ScheduleManager manager;
031    LocationManagerXml managerXml;
032
033    Schedule _schedule = null;
034    ScheduleItem _scheduleItem = null;
035    Location _location = null;
036    public Track _track = null;
037
038    // labels
039    // major buttons
040    JButton addTypeButton = new JButton(Bundle.getMessage("AddType"));
041    JButton saveScheduleButton = new JButton(Bundle.getMessage("SaveSchedule"));
042    JButton deleteScheduleButton = new JButton(Bundle.getMessage("DeleteSchedule"));
043    JButton addScheduleButton = new JButton(Bundle.getMessage("AddSchedule"));
044
045    // check boxes
046    JCheckBox checkBox;
047
048    // radio buttons
049    JRadioButton addLocAtTop = new JRadioButton(Bundle.getMessage("Top"));
050    JRadioButton addLocAtMiddle = new JRadioButton(Bundle.getMessage("Middle"));
051    JRadioButton addLocAtBottom = new JRadioButton(Bundle.getMessage("Bottom"));
052    JRadioButton sequentialRadioButton = new JRadioButton(Bundle.getMessage("Sequential"));
053    JRadioButton matchRadioButton = new JRadioButton(Bundle.getMessage("Match"));
054
055    // text field
056    JTextField scheduleNameTextField = new JTextField(20);
057    JTextField commentTextField = new JTextField(35);
058
059    // combo boxes
060    JComboBox<String> typeBox = new JComboBox<>();
061
062    public static final int MAX_NAME_LENGTH = Control.max_len_string_location_name;
063    public static final String NAME = Bundle.getMessage("Name");
064    public static final String DISPOSE = "dispose"; // NOI18N
065
066    public ScheduleEditFrame(Schedule schedule, Track track) {
067        super();
068
069        _schedule = schedule;
070        _location = track.getLocation();
071        _track = track;
072
073        // load managers
074        manager = InstanceManager.getDefault(ScheduleManager.class);
075        managerXml = InstanceManager.getDefault(LocationManagerXml.class);
076
077        // Set up the jtable in a Scroll Pane..
078        schedulePane = new JScrollPane(scheduleTable);
079        schedulePane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
080
081        scheduleModel.initTable(this, scheduleTable, schedule, _location, _track);
082        if (_schedule != null) {
083            scheduleNameTextField.setText(_schedule.getName());
084            commentTextField.setText(_schedule.getComment());
085            setTitle(Bundle.getMessage("TitleScheduleEdit", _track.getName()));
086            enableButtons(true);
087        } else {
088            setTitle(Bundle.getMessage("TitleScheduleAdd", _track.getName()));
089            enableButtons(false);
090        }
091
092        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
093
094        // Layout the panel by rows
095        JPanel p1 = new JPanel();
096        p1.setLayout(new BoxLayout(p1, BoxLayout.X_AXIS));
097
098        JScrollPane p1Pane = new JScrollPane(p1);
099        p1Pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
100        p1Pane.setMinimumSize(new Dimension(300,
101                3 * scheduleNameTextField.getPreferredSize().height));
102        p1Pane.setMaximumSize(new Dimension(2000, 200));
103
104        // row 1a name
105        JPanel pName = new JPanel();
106        pName.setLayout(new GridBagLayout());
107        pName.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Name")));
108        addItem(pName, scheduleNameTextField, 0, 0);
109
110        // row 1b comment
111        JPanel pC = new JPanel();
112        pC.setLayout(new GridBagLayout());
113        pC.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Comment")));
114        addItem(pC, commentTextField, 0, 0);
115
116        // row 1c mode
117        JPanel pMode = new JPanel();
118        pMode.setLayout(new GridBagLayout());
119        pMode.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("ScheduleMode")));
120        addItem(pMode, sequentialRadioButton, 0, 0);
121        addItem(pMode, matchRadioButton, 1, 0);
122
123        sequentialRadioButton.setToolTipText(Bundle.getMessage("TipSequential"));
124        matchRadioButton.setToolTipText(Bundle.getMessage("TipMatch"));
125        ButtonGroup modeGroup = new ButtonGroup();
126        modeGroup.add(sequentialRadioButton);
127        modeGroup.add(matchRadioButton);
128
129        sequentialRadioButton.setSelected(_track.getScheduleMode() == Track.SEQUENTIAL);
130        matchRadioButton.setSelected(_track.getScheduleMode() == Track.MATCH);
131        scheduleModel.setMatchMode(_track.getScheduleMode() == Track.MATCH);
132
133        p1.add(pName);
134        p1.add(pC);
135        p1.add(pMode);
136
137        // row 2
138        JPanel p3 = new JPanel();
139        p3.setLayout(new GridBagLayout());
140        p3.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("AddItem")));
141        addItem(p3, typeBox, 0, 1);
142        addItem(p3, addTypeButton, 1, 1);
143        addItem(p3, addLocAtTop, 2, 1);
144        addItem(p3, addLocAtMiddle, 3, 1);
145        addItem(p3, addLocAtBottom, 4, 1);
146        ButtonGroup group = new ButtonGroup();
147        group.add(addLocAtTop);
148        group.add(addLocAtMiddle);
149        group.add(addLocAtBottom);
150        addLocAtBottom.setSelected(true);
151
152        p3.setMaximumSize(new Dimension(2000, 200));
153
154        // row 11 buttons
155        JPanel pB = new JPanel();
156        pB.setLayout(new GridBagLayout());
157        pB.setBorder(BorderFactory.createTitledBorder(""));
158        pB.setMaximumSize(new Dimension(2000, 200));
159
160        // row 13
161        addItem(pB, deleteScheduleButton, 0, 0);
162        addItem(pB, addScheduleButton, 1, 0);
163        addItem(pB, saveScheduleButton, 3, 0);
164
165        getContentPane().add(p1Pane);
166        getContentPane().add(schedulePane);
167        getContentPane().add(p3);
168        getContentPane().add(pB);
169
170        // set up buttons
171        addButtonAction(addTypeButton);
172        addButtonAction(deleteScheduleButton);
173        addButtonAction(addScheduleButton);
174        addButtonAction(saveScheduleButton);
175
176        // set up radio buttons
177        addRadioButtonAction(sequentialRadioButton);
178        addRadioButtonAction(matchRadioButton);
179
180        // set up combobox
181        OperationsPanel.padComboBox(typeBox);
182        loadTypeComboBox();
183
184        // build menu
185        JMenuBar menuBar = new JMenuBar();
186        JMenu toolMenu = new JMenu(Bundle.getMessage("MenuTools"));
187        menuBar.add(toolMenu);
188        toolMenu.add(new ScheduleCopyAction(schedule, track));
189        toolMenu.add(new ScheduleOptionsAction(this));
190        toolMenu.add(new ScheduleResetHitsAction(schedule));
191        toolMenu.addSeparator();
192        toolMenu.add(new ExportSchedulesAction());
193        toolMenu.addSeparator();
194        toolMenu.add(new SchedulesByLoadAction());
195        toolMenu.add(new SchedulesAndStagingAction());
196        toolMenu.addSeparator();
197        toolMenu.add(new PrintLocationsAction(false, _location));
198        toolMenu.add(new PrintLocationsAction(true, _location));
199        setJMenuBar(menuBar);
200        addHelpMenu("package.jmri.jmrit.operations.Operations_Schedules", true); // NOI18N
201
202        // get notified if car types or roads are changed
203        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
204        _location.addPropertyChangeListener(this);
205        _track.addPropertyChangeListener(this);
206
207        // set frame size and schedule for display
208        initMinimumSize(new Dimension(Control.panelWidth700, Control.panelHeight400));
209    }
210
211    // Save, Delete, Add
212    @Override
213    public void buttonActionPerformed(java.awt.event.ActionEvent ae) {
214        if (ae.getSource() == addTypeButton) {
215            addNewScheduleItem();
216        }
217        if (ae.getSource() == saveScheduleButton) {
218            log.debug("schedule save button activated");
219            Schedule schedule = manager.getScheduleByName(scheduleNameTextField.getText());
220            if (_schedule == null && schedule == null) {
221                saveNewSchedule();
222            } else {
223                if (schedule != null && schedule != _schedule) {
224                    reportScheduleExists(Bundle.getMessage("save"));
225                    return;
226                }
227                saveSchedule();
228            }
229            if (Setup.isCloseWindowOnSaveEnabled()) {
230                dispose();
231            }
232        }
233        if (ae.getSource() == deleteScheduleButton) {
234            log.debug("schedule delete button activated");
235            if (JmriJOptionPane.showConfirmDialog(this,
236                    Bundle.getMessage("DoYouWantToDeleteSchedule", scheduleNameTextField.getText()),
237                    Bundle.getMessage("DeleteSchedule?"),
238                    JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) {
239                return;
240            }
241            Schedule schedule = manager.getScheduleByName(scheduleNameTextField.getText());
242            if (schedule == null) {
243                return;
244            }
245
246            if (_track != null) {
247                _track.setScheduleId(Track.NONE);
248            }
249
250            manager.deregister(schedule);
251            _schedule = null;
252
253            enableButtons(false);
254            // save schedule file
255            OperationsXml.save();
256        }
257        if (ae.getSource() == addScheduleButton) {
258            Schedule schedule = manager.getScheduleByName(scheduleNameTextField.getText());
259            if (schedule != null) {
260                reportScheduleExists(Bundle.getMessage("add"));
261                return;
262            }
263            saveNewSchedule();
264        }
265    }
266
267    @Override
268    public void radioButtonActionPerformed(java.awt.event.ActionEvent ae) {
269        log.debug("Radio button action");
270        scheduleModel.setMatchMode(ae.getSource() == matchRadioButton);
271    }
272
273    private void addNewScheduleItem() {
274        if (typeBox.getSelectedItem() == null) {
275            return;
276        }
277        // add item to this schedule
278        if (addLocAtTop.isSelected()) {
279            _schedule.addItem((String) typeBox.getSelectedItem(), 0);
280        } else if (addLocAtMiddle.isSelected()) {
281            if (scheduleTable.getSelectedRow() >= 0) {
282                int row = scheduleTable.getSelectedRow();
283                log.debug("Selected row: {}", row);
284                _schedule.addItem((String) typeBox.getSelectedItem(), row);
285                // we need to reselect the table since the content has changed
286                scheduleTable.getSelectionModel().setSelectionInterval(row, row);
287            } else {
288                _schedule.addItem((String) typeBox.getSelectedItem(), _schedule.getSize() / 2);
289            }
290        } else {
291            _schedule.addItem((String) typeBox.getSelectedItem());
292        }
293        if (_track.getScheduleMode() == Track.MATCH && typeBox.getSelectedIndex() < typeBox.getItemCount() - 1) {
294            typeBox.setSelectedIndex(typeBox.getSelectedIndex() + 1);
295        }
296    }
297
298    private void saveNewSchedule() {
299        if (!checkName(Bundle.getMessage("add"))) {
300            return;
301        }
302        Schedule schedule = manager.newSchedule(scheduleNameTextField.getText());
303        scheduleModel.initTable(this, scheduleTable, schedule, _location, _track);
304        _schedule = schedule;
305        // enable checkboxes
306        enableButtons(true);
307        saveSchedule();
308    }
309
310    private void saveSchedule() {
311        if (!checkName(Bundle.getMessage("save"))) {
312            return;
313        }
314        _schedule.setName(scheduleNameTextField.getText());
315        _schedule.setComment(commentTextField.getText());
316
317        if (scheduleTable.isEditing()) {
318            log.debug("schedule table edit true");
319            scheduleTable.getCellEditor().stopCellEditing();
320            scheduleTable.clearSelection();
321        }
322        if (_track != null) {
323            if (!_track.getScheduleId().equals(_schedule.getId())) {
324                InstanceManager.getDefault(LocationManager.class).resetMoves();
325            }
326            _track.setSchedule(_schedule);
327            if (sequentialRadioButton.isSelected()) {
328                _track.setScheduleMode(Track.SEQUENTIAL);
329            } else {
330                _track.setScheduleMode(Track.MATCH);
331            }
332            // check for errors, ignore no schedule items error when creating a
333            // new schedule
334            String status = _track.checkScheduleValid();
335            if (_schedule.getItemsBySequenceList().size() != 0 && !status.equals(Schedule.SCHEDULE_OKAY)) {
336                JmriJOptionPane.showMessageDialog(this, status, Bundle.getMessage("ErrorTitle"),
337                        JmriJOptionPane.ERROR_MESSAGE);
338            }
339        }
340
341        // save schedule file
342        OperationsXml.save();
343    }
344
345    /*
346     * only load car types serviced by the track
347     */
348    private void loadTypeComboBox() {
349        typeBox.removeAllItems();
350        for (String typeName : InstanceManager.getDefault(CarTypes.class).getNames()) {
351            if (_track.isTypeNameAccepted(typeName)) {
352                typeBox.addItem(typeName);
353            }
354        }
355    }
356
357    /**
358     * @return true if name is less than 26 characters
359     */
360    private boolean checkName(String s) {
361        if (scheduleNameTextField.getText().trim().isEmpty()) {
362            return false;
363        }
364        if (scheduleNameTextField.getText().length() > MAX_NAME_LENGTH) {
365            log.error("Schedule name must be less than 26 charaters");
366            JmriJOptionPane.showMessageDialog(this,
367                    Bundle.getMessage("ScheduleNameLengthMax",
368                            Integer.toString(MAX_NAME_LENGTH + 1)),
369                    Bundle.getMessage("CanNotSchedule", s),
370                    JmriJOptionPane.ERROR_MESSAGE);
371            return false;
372        }
373        return true;
374    }
375
376    private void reportScheduleExists(String s) {
377        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ReportExists"),
378                Bundle.getMessage("CanNotSchedule", s),
379                JmriJOptionPane.ERROR_MESSAGE);
380    }
381
382    private void enableButtons(boolean enabled) {
383        typeBox.setEnabled(enabled);
384        addTypeButton.setEnabled(enabled);
385        addLocAtTop.setEnabled(enabled);
386        addLocAtMiddle.setEnabled(enabled);
387        addLocAtBottom.setEnabled(enabled);
388        saveScheduleButton.setEnabled(enabled);
389        deleteScheduleButton.setEnabled(enabled);
390        scheduleTable.setEnabled(enabled);
391        // the inverse!
392        addScheduleButton.setEnabled(!enabled);
393    }
394
395    @Override
396    public void dispose() {
397        InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this);
398        _location.removePropertyChangeListener(this);
399        _track.removePropertyChangeListener(this);
400        InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent(tpm -> {
401            tpm.stopPersisting(scheduleTable);
402        });
403        scheduleModel.dispose();
404        super.dispose();
405    }
406
407    @Override
408    public void propertyChange(java.beans.PropertyChangeEvent e) {
409        if (Control.SHOW_PROPERTY) {
410            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
411                    .getNewValue());
412        }
413        if (e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY) ||
414                e.getPropertyName().equals(Track.TYPES_CHANGED_PROPERTY) ||
415                e.getPropertyName().equals(Location.TYPES_CHANGED_PROPERTY)) {
416            loadTypeComboBox();
417        }
418    }
419
420    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ScheduleEditFrame.class);
421
422}