001package jmri.jmrit.operations.routes;
002
003import java.awt.Dimension;
004import java.awt.GridBagLayout;
005import java.text.MessageFormat;
006import java.util.List;
007
008import javax.swing.*;
009
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013import jmri.InstanceManager;
014import jmri.jmrit.operations.OperationsFrame;
015import jmri.jmrit.operations.OperationsXml;
016import jmri.jmrit.operations.locations.Location;
017import jmri.jmrit.operations.locations.LocationManager;
018import jmri.jmrit.operations.routes.tools.PrintRouteAction;
019import jmri.jmrit.operations.routes.tools.RouteBlockingOrderEditFrameAction;
020import jmri.jmrit.operations.routes.tools.RouteCopyAction;
021import jmri.jmrit.operations.routes.tools.SetTrainIconRouteAction;
022import jmri.jmrit.operations.setup.Control;
023import jmri.jmrit.operations.setup.Setup;
024import jmri.jmrit.operations.trains.Train;
025import jmri.swing.JTablePersistenceManager;
026
027/**
028 * Frame for user edit of route
029 *
030 * @author Dan Boudreau Copyright (C) 2008, 2010, 2011, 2014, 2016
031 */
032public class RouteEditFrame extends OperationsFrame implements java.beans.PropertyChangeListener {
033
034    RouteEditTableModel routeModel = new RouteEditTableModel();
035    JTable routeTable = new JTable(routeModel);
036    JScrollPane routePane;
037
038    RouteManager routeManager;
039
040    Route _route = null;
041    Train _train = null;
042
043    // major buttons
044    JButton addLocationButton = new JButton(Bundle.getMessage("AddLocation"));
045    JButton saveRouteButton = new JButton(Bundle.getMessage("SaveRoute"));
046    JButton deleteRouteButton = new JButton(Bundle.getMessage("DeleteRoute"));
047    JButton addRouteButton = new JButton(Bundle.getMessage("AddRoute"));
048
049    // radio buttons
050    JRadioButton addLocAtTop = new JRadioButton(Bundle.getMessage("Top"));
051    JRadioButton addLocAtMiddle = new JRadioButton(Bundle.getMessage("Middle"));
052    JRadioButton addLocAtBottom = new JRadioButton(Bundle.getMessage("Bottom"));
053
054    JRadioButton showWait = new JRadioButton(Bundle.getMessage("Wait"));
055    JRadioButton showDepartTime = new JRadioButton(Bundle.getMessage("DepartTime"));
056
057    // text field
058    JTextField routeNameTextField = new JTextField(Control.max_len_string_route_name);
059    JTextField commentTextField = new JTextField(35);
060
061    // combo boxes
062    JComboBox<Location> locationBox = InstanceManager.getDefault(LocationManager.class).getComboBox();
063
064    JMenu toolMenu = new JMenu(Bundle.getMessage("MenuTools"));
065
066    public static final String NAME = Bundle.getMessage("Name");
067    public static final String DISPOSE = "dispose"; // NOI18N
068
069    public RouteEditFrame() {
070        super(Bundle.getMessage("TitleRouteEdit"));
071    }
072
073    public void initComponents(Route route, Train train) {
074        _train = train; // assign route to this train
075        initComponents(route);
076    }
077
078    public void initComponents(Route route) {
079
080        _route = route;
081
082        // load managers
083        routeManager = InstanceManager.getDefault(RouteManager.class);
084
085        // Set up the jtable in a Scroll Pane..
086        routePane = new JScrollPane(routeTable);
087        routePane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
088        routePane.setBorder(BorderFactory.createTitledBorder(""));
089
090        routeModel.initTable(this, routeTable, _route);
091
092        if (_route != null) {
093            _route.addPropertyChangeListener(this);
094            routeNameTextField.setText(_route.getName());
095            commentTextField.setText(_route.getComment());
096            enableButtons(!route.getStatus().equals(Route.TRAIN_BUILT)); // do not allow user to modify a built train
097            addRouteButton.setEnabled(false); // override and disable
098        } else {
099            setTitle(Bundle.getMessage("TitleRouteAdd"));
100            enableButtons(false);
101        }
102
103        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
104
105        // Set up the panels
106        JPanel p1 = new JPanel();
107        p1.setLayout(new BoxLayout(p1, BoxLayout.X_AXIS));
108        JScrollPane p1Pane = new JScrollPane(p1);
109        p1Pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
110        p1Pane.setMinimumSize(new Dimension(300, 3 * routeNameTextField.getPreferredSize().height));
111        p1Pane.setMaximumSize(new Dimension(2000, 200));
112        p1Pane.setBorder(BorderFactory.createTitledBorder(""));
113
114        // name panel
115        JPanel pName = new JPanel();
116        pName.setLayout(new GridBagLayout());
117        pName.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Name")));
118        addItem(pName, routeNameTextField, 0, 0);
119
120        // comment panel
121        JPanel pComment = new JPanel();
122        pComment.setLayout(new GridBagLayout());
123        pComment.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Comment")));
124        addItem(pComment, commentTextField, 0, 0);
125
126        p1.add(pName);
127        p1.add(pComment);
128
129        JPanel p2 = new JPanel();
130        p2.setLayout(new BoxLayout(p2, BoxLayout.X_AXIS));
131        JScrollPane p2Pane = new JScrollPane(p2);
132        p2Pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
133        p2Pane.setMinimumSize(new Dimension(300, 3 * routeNameTextField.getPreferredSize().height));
134        p2Pane.setMaximumSize(new Dimension(2000, 200));
135        p2Pane.setBorder(BorderFactory.createTitledBorder(""));
136
137        // location panel
138        JPanel pLoc = new JPanel();
139        pLoc.setLayout(new GridBagLayout());
140        pLoc.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Location")));
141        addItem(pLoc, locationBox, 0, 1);
142        addItem(pLoc, addLocationButton, 1, 1);
143        addItem(pLoc, addLocAtTop, 2, 1);
144        addItem(pLoc, addLocAtMiddle, 3, 1);
145        addItem(pLoc, addLocAtBottom, 4, 1);
146
147        // Wait or Depart Time panel
148        JPanel pWait = new JPanel();
149        pWait.setLayout(new GridBagLayout());
150        pWait.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Display")));
151        addItem(pWait, showWait, 0, 1);
152        addItem(pWait, showDepartTime, 1, 1);
153
154        p2.add(pLoc);
155        p2.add(pWait);
156
157        // row 12 buttons
158        JPanel pB = new JPanel();
159        pB.setLayout(new GridBagLayout());
160        JScrollPane pBPane = new JScrollPane(pB);
161        pBPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
162        pBPane.setMinimumSize(new Dimension(300, 3 * routeNameTextField.getPreferredSize().height));
163        pBPane.setMaximumSize(new Dimension(2000, 200));
164        pBPane.setBorder(BorderFactory.createTitledBorder(""));
165
166        addItem(pB, deleteRouteButton, 0, 0);
167        addItem(pB, addRouteButton, 1, 0);
168        addItem(pB, saveRouteButton, 3, 0);
169
170        getContentPane().add(p1Pane);
171        getContentPane().add(routePane);
172        getContentPane().add(p2Pane);
173        getContentPane().add(pBPane);
174
175        // setup buttons
176        addButtonAction(addLocationButton);
177        addButtonAction(deleteRouteButton);
178        addButtonAction(addRouteButton);
179        addButtonAction(saveRouteButton);
180
181        // setup radio buttons
182        ButtonGroup group = new ButtonGroup();
183        group.add(addLocAtTop);
184        group.add(addLocAtMiddle);
185        group.add(addLocAtBottom);
186        addLocAtBottom.setSelected(true);
187
188        addRadioButtonAction(addLocAtTop); // to clear table row sorting
189        addRadioButtonAction(addLocAtMiddle);
190        addRadioButtonAction(addLocAtBottom); // to clear table row sorting
191
192        ButtonGroup groupTime = new ButtonGroup();
193        groupTime.add(showWait);
194        groupTime.add(showDepartTime);
195        addRadioButtonAction(showWait);
196        addRadioButtonAction(showDepartTime);
197        setTimeWaitRadioButtons();
198
199        // build menu
200        JMenuBar menuBar = new JMenuBar();
201        menuBar.add(toolMenu);
202        loadToolMenu();
203        setJMenuBar(menuBar);
204        addHelpMenu("package.jmri.jmrit.operations.Operations_EditRoute", true); // NOI18N
205
206        // get notified if combo box gets modified
207        InstanceManager.getDefault(LocationManager.class).addPropertyChangeListener(this);
208
209        // set frame size and route for display
210        initMinimumSize(new Dimension(Control.panelWidth700, Control.panelHeight400));
211    }
212
213    private void loadToolMenu() {
214        toolMenu.removeAll();
215        toolMenu.add(new RouteBlockingOrderEditFrameAction(_route));
216        toolMenu.add(new RouteCopyAction(_route));
217        toolMenu.add(new SetTrainIconRouteAction(_route));
218        toolMenu.addSeparator();
219        toolMenu.add(new PrintRouteAction(false, _route));
220        toolMenu.add(new PrintRouteAction(true, _route));
221    }
222
223    // Save, Delete, Add
224    @Override
225    public void buttonActionPerformed(java.awt.event.ActionEvent ae) {
226        if (ae.getSource() == addLocationButton) {
227            log.debug("route add location button activated");
228            if (locationBox.getSelectedItem() != null) {
229                addNewRouteLocation();
230            }
231        }
232        if (ae.getSource() == saveRouteButton) {
233            log.debug("route save button activated");
234            Route route = routeManager.getRouteByName(routeNameTextField.getText());
235            if (_route == null && route == null) {
236                saveNewRoute();
237            } else {
238                if (route != null && route != _route) {
239                    reportRouteExists(Bundle.getMessage("save"));
240                    return;
241                }
242                saveRoute();
243            }
244            if (Setup.isCloseWindowOnSaveEnabled()) {
245                dispose();
246            }
247        }
248        if (ae.getSource() == deleteRouteButton) {
249            log.debug("route delete button activated");
250            if (JOptionPane.showConfirmDialog(this,
251                    MessageFormat.format(Bundle.getMessage("AreYouSure?"),
252                            new Object[] { routeNameTextField.getText() }),
253                    Bundle.getMessage("DeleteRoute?"), JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
254                return;
255            }
256            Route route = routeManager.getRouteByName(routeNameTextField.getText());
257            if (route == null) {
258                return;
259            }
260
261            routeManager.deregister(route);
262            _route = null;
263
264            enableButtons(false);
265            routeModel.dispose();
266            // save route file
267            OperationsXml.save();
268        }
269        if (ae.getSource() == addRouteButton) {
270            Route route = routeManager.getRouteByName(routeNameTextField.getText());
271            if (route != null) {
272                reportRouteExists(Bundle.getMessage("add"));
273                return;
274            }
275            saveNewRoute();
276        }
277    }
278
279    @Override
280    public void radioButtonActionPerformed(java.awt.event.ActionEvent ae) {
281        routeModel.setWait(showWait.isSelected());
282    }
283
284    private void addNewRouteLocation() {
285        if (routeTable.isEditing()) {
286            log.debug("route table edit true");
287            routeTable.getCellEditor().stopCellEditing();
288        }
289        // add location to this route
290        Location l = (Location) locationBox.getSelectedItem();
291        RouteLocation rl;
292        if (addLocAtTop.isSelected()) {
293            // add location to start
294            rl = _route.addLocation(l, Route.START);
295        } else if (addLocAtMiddle.isSelected()) {
296            // add location to middle
297            if (routeTable.getSelectedRow() >= 0) {
298                int row = routeTable.getSelectedRow();
299                rl = _route.addLocation(l, row + Route.START);
300                // we need to reselect the table since the content has changed
301                routeTable.getSelectionModel().setSelectionInterval(row + Route.START, row + Route.START);
302            } else {
303                rl = _route.addLocation(l, _route.size() / 2 + Route.START);
304            }
305        } else {
306            // add location to end
307            rl = _route.addLocation(l);
308        }
309        rl.setTrainDirection(routeModel.getLastTrainDirection());
310        rl.setMaxTrainLength(routeModel.getLastMaxTrainLength());
311        if (rl.getLocation().isStaging()) {
312            rl.setMaxCarMoves(50);
313        } else {
314            rl.setMaxCarMoves(routeModel.getLastMaxTrainMoves());
315        }
316        // set train icon location
317        rl.setTrainIconCoordinates();
318    }
319
320    private void saveNewRoute() {
321        if (!checkName(Bundle.getMessage("add"))) {
322            return;
323        }
324        Route route = routeManager.newRoute(routeNameTextField.getText());
325        routeModel.initTable(this, routeTable, route);
326        _route = route;
327        enableButtons(true);
328        // assign route to a train?
329        if (_train != null) {
330            _train.setRoute(route);
331        }
332        if (_route != null) {
333            _route.addPropertyChangeListener(this);
334        }
335        saveRoute();
336        loadToolMenu();
337    }
338
339    private void saveRoute() {
340        if (!checkName(Bundle.getMessage("save"))) {
341            return;
342        }
343        _route.setName(routeNameTextField.getText());
344        _route.setComment(commentTextField.getText());
345
346        if (routeTable.isEditing()) {
347            log.debug("route table edit true");
348            routeTable.getCellEditor().stopCellEditing();
349        }
350        checkTrainDirections();
351        // save route file
352        OperationsXml.save();
353    }
354
355    /**
356     *
357     * @return true if name is length is okay
358     */
359    private boolean checkName(String s) {
360        if (routeNameTextField.getText().trim().isEmpty()) {
361            log.debug("Must enter a name for the route");
362            JOptionPane.showMessageDialog(this, Bundle.getMessage("MustEnterName"),
363                    MessageFormat.format(Bundle.getMessage("CanNotRoute"), new Object[] { s }),
364                    JOptionPane.ERROR_MESSAGE);
365            return false;
366        }
367        if (routeNameTextField.getText().length() > Control.max_len_string_route_name) {
368            JOptionPane.showMessageDialog(this,
369                    MessageFormat.format(Bundle.getMessage("RouteNameLess"),
370                            new Object[] { Control.max_len_string_route_name + 1 }),
371                    MessageFormat.format(Bundle.getMessage("CanNotRoute"), new Object[] { s }),
372                    JOptionPane.ERROR_MESSAGE);
373            return false;
374        }
375        return true;
376    }
377
378    /*
379     * Checks to see if user has disabled the saved train directions for this route.
380     */
381    private void checkTrainDirections() {
382        // get the valid train directions
383        List<String> directions = Setup.getTrainDirectionList();
384        for (RouteLocation rl : _route.getLocationsBySequenceList()) {
385            if (!directions.contains(rl.getTrainDirectionString())) {
386                JOptionPane.showMessageDialog(this,
387                        MessageFormat.format(Bundle.getMessage("RouteDirection"), new Object[] { rl.getId() }),
388                        MessageFormat.format(Bundle.getMessage("RouteDirectionError"),
389                                new Object[] { rl.getTrainDirectionString() }),
390                        JOptionPane.ERROR_MESSAGE);
391                return;
392            }
393        }
394    }
395
396    private void reportRouteExists(String s) {
397        log.info("Can not {}, route already exists", s);
398        JOptionPane.showMessageDialog(this, Bundle.getMessage("ReportExists"),
399                MessageFormat.format(Bundle.getMessage("CanNotRoute"), new Object[] { s }), JOptionPane.ERROR_MESSAGE);
400    }
401
402    private void enableButtons(boolean enabled) {
403        toolMenu.setEnabled(enabled);
404        locationBox.setEnabled(enabled);
405        addLocationButton.setEnabled(enabled);
406        addLocAtTop.setEnabled(enabled);
407        addLocAtMiddle.setEnabled(enabled);
408        addLocAtBottom.setEnabled(enabled);
409        saveRouteButton.setEnabled(enabled);
410        deleteRouteButton.setEnabled(enabled);
411        routeTable.setEnabled(enabled);
412        showWait.setEnabled(enabled);
413        showDepartTime.setEnabled(enabled);
414        // the inverse!
415        addRouteButton.setEnabled(!enabled);
416    }
417
418    @Override
419    public void dispose() {
420        InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent(tpm -> {
421            tpm.stopPersisting(routeTable);
422        });
423        if (_route != null) {
424            _route.removePropertyChangeListener(this);
425        }
426        routeModel.dispose();
427        super.dispose();
428    }
429
430    private void updateComboBoxes() {
431        InstanceManager.getDefault(LocationManager.class).updateComboBox(locationBox);
432    }
433
434    // if the route has a departure time in the first location set the
435    // showDepartTime radio button
436    private void setTimeWaitRadioButtons() {
437        showWait.setSelected(true);
438        if (_route != null) {
439            RouteLocation rl = _route.getDepartsRouteLocation();
440            if (rl != null && !rl.getDepartureTime().equals(RouteLocation.NONE)) {
441                showDepartTime.setSelected(true);
442            }
443            routeModel.setWait(showWait.isSelected());
444        }
445    }
446
447    @Override
448    public void propertyChange(java.beans.PropertyChangeEvent e) {
449        if (Control.SHOW_PROPERTY) {
450            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(),
451                    e.getNewValue());
452        }
453        if (e.getPropertyName().equals(LocationManager.LISTLENGTH_CHANGED_PROPERTY)) {
454            updateComboBoxes();
455        }
456        if (e.getPropertyName().equals(Route.ROUTE_STATUS_CHANGED_PROPERTY)) {
457            enableButtons(!_route.getStatus().equals(Route.TRAIN_BUILT)); // do not allow user to modify a built train
458            addRouteButton.setEnabled(false); // override and disable
459        }
460    }
461
462    private final static Logger log = LoggerFactory.getLogger(RouteEditFrame.class);
463}