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