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