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.OperationsFrame;
010import jmri.jmrit.operations.OperationsXml;
011import jmri.jmrit.operations.locations.*;
012import jmri.jmrit.operations.locations.schedules.tools.*;
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        loadTypeComboBox();
182
183        // build menu
184        JMenuBar menuBar = new JMenuBar();
185        JMenu toolMenu = new JMenu(Bundle.getMessage("MenuTools"));
186        menuBar.add(toolMenu);
187        toolMenu.add(new ScheduleCopyAction(schedule));
188        toolMenu.add(new ScheduleOptionsAction(this));
189        toolMenu.add(new ScheduleResetHitsAction(schedule));
190        toolMenu.add(new SchedulesByLoadAction());
191        toolMenu.add(new SchedulesAndStagingAction());
192        setJMenuBar(menuBar);
193        addHelpMenu("package.jmri.jmrit.operations.Operations_Schedules", true); // NOI18N
194
195        // get notified if car types or roads are changed
196        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
197        _location.addPropertyChangeListener(this);
198        _track.addPropertyChangeListener(this);
199
200        // set frame size and schedule for display
201        initMinimumSize(new Dimension(Control.panelWidth700, Control.panelHeight400));
202    }
203
204    // Save, Delete, Add
205    @Override
206    public void buttonActionPerformed(java.awt.event.ActionEvent ae) {
207        if (ae.getSource() == addTypeButton) {
208            addNewScheduleItem();
209        }
210        if (ae.getSource() == saveScheduleButton) {
211            log.debug("schedule save button activated");
212            Schedule schedule = manager.getScheduleByName(scheduleNameTextField.getText());
213            if (_schedule == null && schedule == null) {
214                saveNewSchedule();
215            } else {
216                if (schedule != null && schedule != _schedule) {
217                    reportScheduleExists(Bundle.getMessage("save"));
218                    return;
219                }
220                saveSchedule();
221            }
222            if (Setup.isCloseWindowOnSaveEnabled()) {
223                dispose();
224            }
225        }
226        if (ae.getSource() == deleteScheduleButton) {
227            log.debug("schedule delete button activated");
228            if (JmriJOptionPane.showConfirmDialog(this,
229                    Bundle.getMessage("DoYouWantToDeleteSchedule", scheduleNameTextField.getText()),
230                    Bundle.getMessage("DeleteSchedule?"),
231                    JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) {
232                return;
233            }
234            Schedule schedule = manager.getScheduleByName(scheduleNameTextField.getText());
235            if (schedule == null) {
236                return;
237            }
238
239            if (_track != null) {
240                _track.setScheduleId(Track.NONE);
241            }
242
243            manager.deregister(schedule);
244            _schedule = null;
245
246            enableButtons(false);
247            // save schedule file
248            OperationsXml.save();
249        }
250        if (ae.getSource() == addScheduleButton) {
251            Schedule schedule = manager.getScheduleByName(scheduleNameTextField.getText());
252            if (schedule != null) {
253                reportScheduleExists(Bundle.getMessage("add"));
254                return;
255            }
256            saveNewSchedule();
257        }
258    }
259
260    @Override
261    public void radioButtonActionPerformed(java.awt.event.ActionEvent ae) {
262        log.debug("Radio button action");
263        scheduleModel.setMatchMode(ae.getSource() == matchRadioButton);
264    }
265
266    private void addNewScheduleItem() {
267        if (typeBox.getSelectedItem() == null) {
268            return;
269        }
270        // add item to this schedule
271        if (addLocAtTop.isSelected()) {
272            _schedule.addItem((String) typeBox.getSelectedItem(), 0);
273        } else if (addLocAtMiddle.isSelected()) {
274            if (scheduleTable.getSelectedRow() >= 0) {
275                int row = scheduleTable.getSelectedRow();
276                log.debug("Selected row: {}", row);
277                _schedule.addItem((String) typeBox.getSelectedItem(), row);
278                // we need to reselect the table since the content has changed
279                scheduleTable.getSelectionModel().setSelectionInterval(row, row);
280            } else {
281                _schedule.addItem((String) typeBox.getSelectedItem(), _schedule.getSize() / 2);
282            }
283        } else {
284            _schedule.addItem((String) typeBox.getSelectedItem());
285        }
286        if (_track.getScheduleMode() == Track.MATCH && typeBox.getSelectedIndex() < typeBox.getItemCount() - 1) {
287            typeBox.setSelectedIndex(typeBox.getSelectedIndex() + 1);
288        }
289    }
290
291    private void saveNewSchedule() {
292        if (!checkName(Bundle.getMessage("add"))) {
293            return;
294        }
295        Schedule schedule = manager.newSchedule(scheduleNameTextField.getText());
296        scheduleModel.initTable(this, scheduleTable, schedule, _location, _track);
297        _schedule = schedule;
298        // enable checkboxes
299        enableButtons(true);
300        saveSchedule();
301    }
302
303    private void saveSchedule() {
304        if (!checkName(Bundle.getMessage("save"))) {
305            return;
306        }
307        _schedule.setName(scheduleNameTextField.getText());
308        _schedule.setComment(commentTextField.getText());
309
310        if (scheduleTable.isEditing()) {
311            log.debug("schedule table edit true");
312            scheduleTable.getCellEditor().stopCellEditing();
313            scheduleTable.clearSelection();
314        }
315        if (_track != null) {
316            if (!_track.getScheduleId().equals(_schedule.getId())) {
317                InstanceManager.getDefault(LocationManager.class).resetMoves();
318            }
319            _track.setSchedule(_schedule);
320            if (sequentialRadioButton.isSelected()) {
321                _track.setScheduleMode(Track.SEQUENTIAL);
322            } else {
323                _track.setScheduleMode(Track.MATCH);
324            }
325            // check for errors, ignore no schedule items error when creating a
326            // new schedule
327            String status = _track.checkScheduleValid();
328            if (_schedule.getItemsBySequenceList().size() != 0 && !status.equals(Schedule.SCHEDULE_OKAY)) {
329                JmriJOptionPane.showMessageDialog(this, status, Bundle.getMessage("ErrorTitle"),
330                        JmriJOptionPane.ERROR_MESSAGE);
331            }
332        }
333
334        // save schedule file
335        OperationsXml.save();
336    }
337
338    private void loadTypeComboBox() {
339        typeBox.removeAllItems();
340        for (String typeName : InstanceManager.getDefault(CarTypes.class).getNames()) {
341            if (_track.isTypeNameAccepted(typeName)) {
342                typeBox.addItem(typeName);
343            }
344        }
345    }
346
347    /**
348     * @return true if name is less than 26 characters
349     */
350    private boolean checkName(String s) {
351        if (scheduleNameTextField.getText().trim().isEmpty()) {
352            return false;
353        }
354        if (scheduleNameTextField.getText().length() > MAX_NAME_LENGTH) {
355            log.error("Schedule name must be less than 26 charaters");
356            JmriJOptionPane.showMessageDialog(this,
357                    Bundle.getMessage("ScheduleNameLengthMax",
358                            Integer.toString(MAX_NAME_LENGTH + 1)),
359                    Bundle.getMessage("CanNotSchedule", s),
360                    JmriJOptionPane.ERROR_MESSAGE);
361            return false;
362        }
363        return true;
364    }
365
366    private void reportScheduleExists(String s) {
367        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ReportExists"),
368                Bundle.getMessage("CanNotSchedule", s),
369                JmriJOptionPane.ERROR_MESSAGE);
370    }
371
372    private void enableButtons(boolean enabled) {
373        typeBox.setEnabled(enabled);
374        addTypeButton.setEnabled(enabled);
375        addLocAtTop.setEnabled(enabled);
376        addLocAtMiddle.setEnabled(enabled);
377        addLocAtBottom.setEnabled(enabled);
378        saveScheduleButton.setEnabled(enabled);
379        deleteScheduleButton.setEnabled(enabled);
380        scheduleTable.setEnabled(enabled);
381        // the inverse!
382        addScheduleButton.setEnabled(!enabled);
383    }
384
385    @Override
386    public void dispose() {
387        InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this);
388        _location.removePropertyChangeListener(this);
389        _track.removePropertyChangeListener(this);
390        InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent(tpm -> {
391            tpm.stopPersisting(scheduleTable);
392        });
393        scheduleModel.dispose();
394        super.dispose();
395    }
396
397    @Override
398    public void propertyChange(java.beans.PropertyChangeEvent e) {
399        if (Control.SHOW_PROPERTY) {
400            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
401                    .getNewValue());
402        }
403        if (e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY) ||
404                e.getPropertyName().equals(Track.TYPES_CHANGED_PROPERTY) ||
405                e.getPropertyName().equals(Location.TYPES_CHANGED_PROPERTY)) {
406            loadTypeComboBox();
407        }
408    }
409
410    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ScheduleEditFrame.class);
411
412}