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.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    private void loadTypeComboBox() {
340        typeBox.removeAllItems();
341        for (String typeName : InstanceManager.getDefault(CarTypes.class).getNames()) {
342            if (_track.isTypeNameAccepted(typeName)) {
343                typeBox.addItem(typeName);
344            }
345        }
346    }
347
348    /**
349     * @return true if name is less than 26 characters
350     */
351    private boolean checkName(String s) {
352        if (scheduleNameTextField.getText().trim().isEmpty()) {
353            return false;
354        }
355        if (scheduleNameTextField.getText().length() > MAX_NAME_LENGTH) {
356            log.error("Schedule name must be less than 26 charaters");
357            JmriJOptionPane.showMessageDialog(this,
358                    Bundle.getMessage("ScheduleNameLengthMax",
359                            Integer.toString(MAX_NAME_LENGTH + 1)),
360                    Bundle.getMessage("CanNotSchedule", s),
361                    JmriJOptionPane.ERROR_MESSAGE);
362            return false;
363        }
364        return true;
365    }
366
367    private void reportScheduleExists(String s) {
368        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ReportExists"),
369                Bundle.getMessage("CanNotSchedule", s),
370                JmriJOptionPane.ERROR_MESSAGE);
371    }
372
373    private void enableButtons(boolean enabled) {
374        typeBox.setEnabled(enabled);
375        addTypeButton.setEnabled(enabled);
376        addLocAtTop.setEnabled(enabled);
377        addLocAtMiddle.setEnabled(enabled);
378        addLocAtBottom.setEnabled(enabled);
379        saveScheduleButton.setEnabled(enabled);
380        deleteScheduleButton.setEnabled(enabled);
381        scheduleTable.setEnabled(enabled);
382        // the inverse!
383        addScheduleButton.setEnabled(!enabled);
384    }
385
386    @Override
387    public void dispose() {
388        InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this);
389        _location.removePropertyChangeListener(this);
390        _track.removePropertyChangeListener(this);
391        InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent(tpm -> {
392            tpm.stopPersisting(scheduleTable);
393        });
394        scheduleModel.dispose();
395        super.dispose();
396    }
397
398    @Override
399    public void propertyChange(java.beans.PropertyChangeEvent e) {
400        if (Control.SHOW_PROPERTY) {
401            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
402                    .getNewValue());
403        }
404        if (e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY) ||
405                e.getPropertyName().equals(Track.TYPES_CHANGED_PROPERTY) ||
406                e.getPropertyName().equals(Location.TYPES_CHANGED_PROPERTY)) {
407            loadTypeComboBox();
408        }
409    }
410
411    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ScheduleEditFrame.class);
412
413}