001package jmri.jmrit.operations.trains.schedules;
002
003import java.awt.*;
004import java.beans.PropertyChangeEvent;
005import java.beans.PropertyChangeListener;
006import java.util.Enumeration;
007import java.util.List;
008
009import javax.swing.*;
010
011import jmri.InstanceManager;
012import jmri.jmrit.operations.OperationsFrame;
013import jmri.jmrit.operations.OperationsXml;
014import jmri.jmrit.operations.locations.Location;
015import jmri.jmrit.operations.locations.LocationManager;
016import jmri.jmrit.operations.setup.Control;
017import jmri.jmrit.operations.setup.Setup;
018import jmri.jmrit.operations.trains.Train;
019import jmri.jmrit.operations.trains.TrainManager;
020import jmri.jmrit.operations.trains.excel.TrainCustomManifest;
021import jmri.swing.JTablePersistenceManager;
022import jmri.util.swing.JmriJOptionPane;
023
024/**
025 * Frame for adding and editing train schedules for operations.
026 *
027 * @author Bob Jacobsen Copyright (C) 2001
028 * @author Daniel Boudreau Copyright (C) 2010, 2012, 2016
029 */
030public class TrainsScheduleTableFrame extends OperationsFrame implements PropertyChangeListener {
031
032    TrainManager trainManager = InstanceManager.getDefault(TrainManager.class);
033    TrainScheduleManager trainScheduleManager = InstanceManager.getDefault(TrainScheduleManager.class);
034    LocationManager locationManager = InstanceManager.getDefault(LocationManager.class);
035
036    TrainsScheduleTableModel trainsScheduleModel = new TrainsScheduleTableModel();
037    javax.swing.JTable trainsScheduleTable = new javax.swing.JTable(trainsScheduleModel);
038    JScrollPane trainsPane;
039
040    // labels
041    JLabel textSort = new JLabel(Bundle.getMessage("SortBy"));
042
043    // radio buttons
044    JRadioButton sortByName = new JRadioButton(Bundle.getMessage("Name"));
045    JRadioButton sortByTime = new JRadioButton(Bundle.getMessage("Time"));
046
047    JRadioButton noneButton = new JRadioButton(Bundle.getMessage("None"));
048    JRadioButton anyButton = new JRadioButton(Bundle.getMessage("Any"));
049
050    // radio button groups
051    ButtonGroup schGroup = new ButtonGroup();
052
053    // major buttons
054    JButton selectButton = new JButton(Bundle.getMessage("SelectAll"));
055    JButton clearButton = new JButton(Bundle.getMessage("ClearAll"));
056
057    JButton applyButton = new JButton(Bundle.getMessage("ButtonApply"));
058    JButton buildButton = new JButton(Bundle.getMessage("Build"));
059    JButton printButton = new JButton(Bundle.getMessage("Print"));
060    JButton runFileButton = new JButton(Bundle.getMessage("RunFile"));
061    JButton switchListsButton = new JButton();
062    JButton terminateButton = new JButton(Bundle.getMessage("Terminate"));
063
064    JButton activateButton = new JButton(Bundle.getMessage("Activate"));
065    JButton saveButton = new JButton(Bundle.getMessage("ButtonSave"));
066
067    // check boxes
068    // panel
069    JPanel schedule = new JPanel();
070
071    // text area
072    JTextArea commentTextArea = new JTextArea(2, 70);
073    JScrollPane commentScroller = new JScrollPane(commentTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
074            JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
075
076    public TrainsScheduleTableFrame() {
077
078        // general GUI configuration
079        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
080
081        // Set up the jtable in a Scroll Pane..
082        trainsPane = new JScrollPane(trainsScheduleTable);
083        trainsPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
084        trainsPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
085        trainsScheduleModel.initTable(trainsScheduleTable, this);
086
087        // row comment
088        JPanel pC = new JPanel();
089        pC.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Comment")));
090        pC.setLayout(new GridBagLayout());
091        addItem(pC, commentScroller, 1, 0);
092
093        // adjust text area width based on window size
094        adjustTextAreaColumnWidth(commentScroller, commentTextArea);
095
096        // Set up the control panel
097        // row 1
098        JPanel cp1 = new JPanel();
099        cp1.setLayout(new BoxLayout(cp1, BoxLayout.X_AXIS));
100
101        // row 1
102        JPanel sortBy = new JPanel();
103        sortBy.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("SortBy")));
104        sortBy.add(sortByTime);
105        sortBy.add(sortByName);
106
107        // row 2
108        schedule.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Active")));
109        updateControlPanel();
110
111        cp1.add(sortBy);
112        cp1.add(schedule);
113
114        JPanel pButtons = new JPanel();
115        pButtons.setLayout(new BoxLayout(pButtons, BoxLayout.X_AXIS));
116
117        JPanel cp3 = new JPanel();
118        cp3.setBorder(BorderFactory.createTitledBorder(""));
119        cp3.add(clearButton);
120        cp3.add(selectButton);
121
122        JPanel cp4 = new JPanel();
123        cp4.setBorder(BorderFactory.createTitledBorder(""));
124        cp4.add(applyButton);
125        cp4.add(buildButton);
126        cp4.add(printButton);
127        cp4.add(runFileButton);
128        cp4.add(switchListsButton);
129        cp4.add(terminateButton);
130
131        JPanel cp5 = new JPanel();
132        cp5.setBorder(BorderFactory.createTitledBorder(""));
133        cp5.add(activateButton);
134        cp5.add(saveButton);
135
136        pButtons.add(cp3);
137        pButtons.add(cp4);
138        pButtons.add(cp5);
139
140        // tool tips
141        selectButton.setToolTipText(Bundle.getMessage("SelectAllButtonTip"));
142        clearButton.setToolTipText(Bundle.getMessage("ClearAllButtonTip"));
143        applyButton.setToolTipText(Bundle.getMessage("ApplyButtonTip"));
144        buildButton.setToolTipText(Bundle.getMessage("BuildSelectedTip"));
145        runFileButton.setToolTipText(Bundle.getMessage("RunFileButtonTip"));
146        activateButton.setToolTipText(Bundle.getMessage("ActivateButtonTip"));
147        terminateButton.setToolTipText(Bundle.getMessage("TerminateSelectedTip"));
148
149        setPrintButtonText();
150        setSwitchListButtonText();
151
152        // place controls in scroll pane
153        JPanel controlPanel = new JPanel();
154        controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS));
155        controlPanel.add(pC);
156        controlPanel.add(cp1);
157        controlPanel.add(pButtons);
158
159        JScrollPane controlPane = new JScrollPane(controlPanel);
160        // make sure control panel is the right size
161        controlPane.setMinimumSize(new Dimension(500, 480));
162        controlPane.setMaximumSize(new Dimension(2000, 500));
163        controlPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
164
165        getContentPane().add(trainsPane);
166        getContentPane().add(controlPane);
167
168        // show run button only if create CSV files is enabled
169        updateRunButton();
170
171        // setup buttons
172        addButtonAction(clearButton);
173        addButtonAction(selectButton);
174        addButtonAction(applyButton);
175        addButtonAction(buildButton);
176        addButtonAction(printButton);
177        addButtonAction(runFileButton);
178        addButtonAction(switchListsButton);
179        addButtonAction(terminateButton);
180        addButtonAction(activateButton);
181        addButtonAction(saveButton);
182
183        ButtonGroup sortGroup = new ButtonGroup();
184        sortGroup.add(sortByTime);
185        sortGroup.add(sortByName);
186        sortByTime.setSelected(true);
187
188        addRadioButtonAction(sortByTime);
189        addRadioButtonAction(sortByName);
190
191        addRadioButtonAction(noneButton);
192        addRadioButtonAction(anyButton);
193        
194        // tips
195        noneButton.setToolTipText(Bundle.getMessage("NoActiveTip"));
196        anyButton.setToolTipText(Bundle.getMessage("AnyActiveTip"));
197
198        // build menu
199        JMenuBar menuBar = new JMenuBar();
200        JMenu toolMenu = new JMenu(Bundle.getMessage("MenuTools"));
201        toolMenu.add(new TrainsScheduleEditAction());
202        menuBar.add(toolMenu);
203        setJMenuBar(menuBar);
204
205        // add help menu to window
206        addHelpMenu("package.jmri.jmrit.operations.Operations_TrainSchedules", true); // NOI18N
207
208        setTitle(Bundle.getMessage("TitleScheduleTrains"));
209
210        initMinimumSize(new Dimension(Control.panelWidth700, Control.panelHeight500));
211
212        addHorizontalScrollBarKludgeFix(controlPane, controlPanel);
213
214        Setup.getDefault().addPropertyChangeListener(this);
215        trainManager.addPropertyChangeListener(this);
216        trainScheduleManager.addPropertyChangeListener(this);
217        addPropertyChangeLocations();
218        addPropertyChangeTrainSchedules();
219    }
220
221    @Override
222    public void radioButtonActionPerformed(java.awt.event.ActionEvent ae) {
223        log.debug("radio button activated");
224        // clear any sorts by column
225        clearTableSort(trainsScheduleTable);
226        if (ae.getSource() == sortByName) {
227            trainsScheduleModel.setSort(trainsScheduleModel.SORTBYNAME);
228        } else if (ae.getSource() == sortByTime) {
229            trainsScheduleModel.setSort(trainsScheduleModel.SORTBYTIME);
230        } else if (ae.getSource() == noneButton || ae.getSource() == anyButton) {
231            enableButtons(false);
232            commentTextArea.setText(""); // no text for the noneButton or anyButton
233            // must be one of the schedule radio buttons
234        } else {
235            enableButtons(true);
236            // update comment field
237            TrainSchedule ts = trainScheduleManager.getScheduleById(getSelectedScheduleId());
238            commentTextArea.setText(ts.getComment());
239        }
240    }
241
242    // add, build, print, switch lists, terminate, and save buttons
243    @Override
244    public void buttonActionPerformed(java.awt.event.ActionEvent ae) {
245        log.debug("button activated");
246        if (ae.getSource() == clearButton) {
247            updateCheckboxes(false);
248        }
249        if (ae.getSource() == selectButton) {
250            updateCheckboxes(true);
251        }
252        if (ae.getSource() == applyButton) {
253            applySchedule();
254        }
255        if (ae.getSource() == buildButton) {
256            switchListsButton.setEnabled(false);
257            runFileButton.setEnabled(false);
258            // uses a thread which allows table updates during build
259            trainManager.buildSelectedTrains(getSortByList());
260        }
261        if (ae.getSource() == printButton) {
262            trainManager.printSelectedTrains(getSortByList());
263        }
264        if (ae.getSource() == runFileButton) {
265            // Processes the CSV Manifest files using an external custom program.
266            if (!InstanceManager.getDefault(TrainCustomManifest.class).excelFileExists()) {
267                log.warn("Manifest creator file not found!, directory path: {}, file name: {}",
268                        InstanceManager.getDefault(TrainCustomManifest.class).getDirectoryPathName(),
269                        InstanceManager.getDefault(TrainCustomManifest.class).getFileName()); // NOI18N
270                JmriJOptionPane.showMessageDialog(this,
271                        Bundle.getMessage("LoadDirectoryNameFileName",
272                                InstanceManager.getDefault(TrainCustomManifest.class).getDirectoryPathName(),
273                                        InstanceManager.getDefault(TrainCustomManifest.class).getFileName()),
274                        Bundle.getMessage("ManifestCreatorNotFound"), JmriJOptionPane.ERROR_MESSAGE);
275                return;
276            }
277            List<Train> trains = getSortByList();
278            for (Train train : trains) {
279                if (train.isBuildEnabled()) {
280                    if (!train.isBuilt()) {
281                        JmriJOptionPane.showMessageDialog(this,
282                                Bundle.getMessage("NeedToBuildBeforeRunFile",
283                                        train.getName()),
284                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
285                    } else {
286                        // Add csv manifest file to our collection to be processed.
287                        InstanceManager.getDefault(TrainCustomManifest.class).addCsvFile(train.createCsvManifestFile());
288                        train.setPrinted(true);
289                    }
290                }
291            }
292
293            // Now run the user specified custom Manifest processor program
294            InstanceManager.getDefault(TrainCustomManifest.class).process();
295        }
296        if (ae.getSource() == switchListsButton) {
297            trainScheduleManager.buildSwitchLists();
298        }
299        if (ae.getSource() == terminateButton) {
300            trainManager.terminateSelectedTrains(getSortByList());
301        }
302        if (ae.getSource() == activateButton) {
303            trainScheduleManager.setTrainScheduleActiveId(getSelectedScheduleId());
304            activateButton.setEnabled(false);
305        }
306        if (ae.getSource() == saveButton) {
307            storeValues();
308            if (Setup.isCloseWindowOnSaveEnabled()) {
309                dispose();
310            }
311        }
312    }
313
314    /*
315     * Update radio button names in the same order as the table
316     */
317    private void updateControlPanel() {
318        schedule.removeAll();
319        noneButton.setName(TrainSchedule.NONE); // Name holds schedule id for the selected radio button
320        noneButton.setSelected(true);
321        commentTextArea.setText(""); // no text for the noneButton or anyButton
322        enableButtons(false);
323        schedule.add(noneButton);
324        schGroup.add(noneButton);
325
326        for (int i = trainsScheduleModel.getFixedColumn(); i < trainsScheduleModel.getColumnCount(); i++) {
327            log.debug("Column name: {}", trainsScheduleTable.getColumnName(i));
328            TrainSchedule ts = trainScheduleManager.getScheduleByName(trainsScheduleTable.getColumnName(i));
329            if (ts != null) {
330                JRadioButton b = new JRadioButton();
331                b.setText(ts.getName());
332                b.setName(ts.getId());
333                schedule.add(b);
334                schGroup.add(b);
335                addRadioButtonAction(b);
336                if (b.getName().equals(trainScheduleManager.getTrainScheduleActiveId())) {
337                    b.setSelected(true);
338                    enableButtons(true);
339                    // update comment field
340                    commentTextArea.setText(ts.getComment());
341                }
342            }
343        }
344        anyButton.setName(TrainSchedule.ANY); // Name holds schedule id for the selected radio button
345        schedule.add(anyButton);
346        schGroup.add(anyButton);
347        anyButton.setSelected(trainScheduleManager.getTrainScheduleActiveId().equals(TrainSchedule.ANY));
348        schedule.revalidate();
349    }
350
351    private void updateCheckboxes(boolean selected) {
352        TrainSchedule ts = trainScheduleManager.getScheduleById(getSelectedScheduleId());
353        if (ts != null) {
354            for (Train train : trainManager.getTrainsByIdList()) {
355                if (selected) {
356                    ts.addTrainId(train.getId());
357                } else {
358                    ts.removeTrainId(train.getId());
359                }
360            }
361        }
362    }
363
364    private void applySchedule() {
365        TrainSchedule ts = trainScheduleManager.getScheduleById(getSelectedScheduleId());
366        if (ts != null) {
367            for (Train train : trainManager.getTrainsByIdList()) {
368                train.setBuildEnabled(ts.containsTrainId(train.getId()));
369            }
370        }
371    }
372
373    private String getSelectedScheduleId() {
374        AbstractButton b;
375        Enumeration<AbstractButton> en = schGroup.getElements();
376        while (en.hasMoreElements()) {
377            b = en.nextElement();
378            if (b.isSelected()) {
379                log.debug("schedule radio button {}", b.getText());
380                return b.getName();
381            }
382        }
383        return null;
384    }
385
386    private void enableButtons(boolean enable) {
387        selectButton.setEnabled(enable);
388        clearButton.setEnabled(enable);
389        applyButton.setEnabled(enable);
390        buildButton.setEnabled(enable);
391        printButton.setEnabled(enable);
392        runFileButton.setEnabled(enable);
393        switchListsButton.setEnabled(enable);
394        terminateButton.setEnabled(enable);
395
396        log.debug("Selected id: {}, Active id: {}", getSelectedScheduleId(),
397                trainScheduleManager.getTrainScheduleActiveId());
398
399        activateButton.setEnabled(getSelectedScheduleId() != null &&
400                !getSelectedScheduleId().equals(trainScheduleManager.getTrainScheduleActiveId()));
401
402        commentTextArea.setEnabled(enable);
403    }
404
405    private List<Train> getSortByList() {
406        if (sortByTime.isSelected()) {
407            return trainManager.getTrainsByTimeList();
408        } else {
409            return trainManager.getTrainsByNameList();
410        }
411    }
412
413    private void setSwitchListButtonText() {
414        if (!Setup.isSwitchListRealTime()) {
415            switchListsButton.setText(Bundle.getMessage("Update"));
416        } else if (trainManager.isPrintPreviewEnabled()) {
417            switchListsButton.setText(Bundle.getMessage("PreviewSwitchLists"));
418        } else {
419            switchListsButton.setText(Bundle.getMessage("PrintSwitchLists"));
420        }
421    }
422
423    // Modifies button text and tool tips
424    private void setPrintButtonText() {
425        if (trainManager.isPrintPreviewEnabled()) {
426            printButton.setText(Bundle.getMessage("Preview"));
427            printButton.setToolTipText(Bundle.getMessage("PreviewSelectedTip"));
428        } else {
429            printButton.setText(Bundle.getMessage("Print"));
430            printButton.setToolTipText(Bundle.getMessage("PrintSelectedTip"));
431        }
432    }
433
434    private void updateSwitchListButton() {
435        List<Location> locations = locationManager.getList();
436        for (Location location : locations) {
437            if (location != null && location.isSwitchListEnabled() && location.getStatus().equals(Location.MODIFIED)) {
438                switchListsButton.setBackground(Color.RED);
439                return;
440            }
441        }
442        switchListsButton.setBackground(Color.GREEN);
443    }
444
445    private void updateRunButton() {
446        runFileButton.setVisible(Setup.isGenerateCsvManifestEnabled());
447    }
448
449    @Override
450    protected void storeValues() {
451        // Save comment
452        TrainSchedule ts = trainScheduleManager.getScheduleById(getSelectedScheduleId());
453        if (ts != null) {
454            ts.setComment(commentTextArea.getText());
455        }
456        OperationsXml.save();
457    }
458
459    @Override
460    public void dispose() {
461        Setup.getDefault().removePropertyChangeListener(this);
462        trainManager.removePropertyChangeListener(this);
463        trainScheduleManager.removePropertyChangeListener(this);
464        removePropertyChangeTrainSchedules();
465        removePropertyChangeLocations();
466        trainsScheduleModel.dispose();
467        InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent(tpm -> {
468            tpm.stopPersisting(trainsScheduleTable);
469        });
470        super.dispose();
471    }
472
473    private void addPropertyChangeLocations() {
474        for (Location location : locationManager.getList()) {
475            location.addPropertyChangeListener(this);
476        }
477    }
478
479    private void removePropertyChangeLocations() {
480        for (Location location : locationManager.getList()) {
481            location.removePropertyChangeListener(this);
482        }
483    }
484
485    private void addPropertyChangeTrainSchedules() {
486        List<TrainSchedule> trainSchedules = trainScheduleManager.getSchedulesByIdList();
487        for (TrainSchedule ts : trainSchedules) {
488            ts.addPropertyChangeListener(this);
489        }
490    }
491
492    private void removePropertyChangeTrainSchedules() {
493        List<TrainSchedule> trainSchedules = trainScheduleManager.getSchedulesByIdList();
494        for (TrainSchedule ts : trainSchedules) {
495            ts.removePropertyChangeListener(this);
496        }
497    }
498
499    @Override
500    public void propertyChange(PropertyChangeEvent e) {
501        if (Control.SHOW_PROPERTY)
502            log.debug("Property change {} old: {} new: {}", e.getPropertyName(), e.getOldValue(), e.getNewValue());
503        if (e.getPropertyName().equals(TrainScheduleManager.LISTLENGTH_CHANGED_PROPERTY) ||
504                e.getPropertyName().equals(TrainSchedule.NAME_CHANGED_PROPERTY)) {
505            updateControlPanel();
506        }
507        if (e.getPropertyName().equals(TrainManager.PRINTPREVIEW_CHANGED_PROPERTY)) {
508            setPrintButtonText();
509            setSwitchListButtonText();
510        }
511        if (e.getPropertyName().equals(TrainManager.TRAINS_BUILT_CHANGED_PROPERTY)) {
512            switchListsButton.setEnabled(true);
513            runFileButton.setEnabled(true);
514        }
515        if (e.getPropertyName().equals(Setup.REAL_TIME_PROPERTY_CHANGE)) {
516            setSwitchListButtonText();
517        }
518        if (e.getPropertyName().equals(Location.STATUS_CHANGED_PROPERTY) ||
519                e.getPropertyName().equals(Location.SWITCHLIST_CHANGED_PROPERTY)) {
520            log.debug("update switch list button location ({})", e.getSource());
521            updateSwitchListButton();
522        }
523        if (e.getPropertyName().equals(Setup.MANIFEST_CSV_PROPERTY_CHANGE)) {
524            updateRunButton();
525        }
526    }
527
528    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainsScheduleTableFrame.class);
529}