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