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