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