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