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 showTravel = new JRadioButton(Bundle.getMessage("TravelTime"));
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, showTravel, 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(showTravel);
188        groupTime.add(showDepartTime);
189        addRadioButtonAction(showTravel);
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_AddRoute", 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            } else {
225                JmriJOptionPane.showMessageDialog(this,
226                        Bundle.getMessage("SelectLocation"),
227                        Bundle.getMessage("SelectLocation"),
228                        JmriJOptionPane.INFORMATION_MESSAGE);
229            }
230        }
231        if (ae.getSource() == saveRouteButton) {
232            log.debug("route save button activated");
233            Route route = routeManager.getRouteByName(routeNameTextField.getText());
234            if (_route == null && route == null) {
235                saveNewRoute(); // can't happen, save button is disabled
236            } else {
237                if (route != null && route != _route) {
238                    reportRouteExists(Bundle.getMessage("save"));
239                    return;
240                }
241                if (saveRoute() && Setup.isCloseWindowOnSaveEnabled()) {
242                    dispose();
243                }
244            }
245        }
246        if (ae.getSource() == deleteRouteButton) {
247            log.debug("route delete button activated");
248            if (JmriJOptionPane.showConfirmDialog(this,
249                    Bundle.getMessage("AreYouSure?",
250                            routeNameTextField.getText()),
251                    Bundle.getMessage("DeleteRoute?"), JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) {
252                return;
253            }
254            Route route = routeManager.getRouteByName(routeNameTextField.getText());
255            if (route == null) {
256                return;
257            }
258
259            routeManager.deregister(route);
260            _route = null;
261
262            enableButtons(false);
263            routeModel.dispose();
264            // save route file
265            OperationsXml.save();
266        }
267        if (ae.getSource() == addRouteButton) {
268            Route route = routeManager.getRouteByName(routeNameTextField.getText());
269            if (route != null) {
270                reportRouteExists(Bundle.getMessage("add"));
271                return;
272            }
273            saveNewRoute();
274        }
275    }
276
277    @Override
278    public void radioButtonActionPerformed(java.awt.event.ActionEvent ae) {
279        routeModel.setWait(showTravel.isSelected());
280    }
281
282    private void addNewRouteLocation() {
283        if (routeTable.isEditing()) {
284            log.debug("route table edit true");
285            routeTable.getCellEditor().stopCellEditing();
286        }
287        // add location to this route
288        Location l = (Location) locationBox.getSelectedItem();
289        RouteLocation rl;
290        if (addLocAtTop.isSelected()) {
291            // add location to start
292            rl = _route.addLocation(l, Route.START);
293        } else if (addLocAtMiddle.isSelected()) {
294            // add location to middle
295            if (routeTable.getSelectedRow() >= 0) {
296                int row = routeTable.getSelectedRow();
297                rl = _route.addLocation(l, row + Route.START);
298                // we need to reselect the table since the content has changed
299                routeTable.getSelectionModel().setSelectionInterval(row + Route.START, row + Route.START);
300            } else {
301                rl = _route.addLocation(l, _route.size() / 2 + Route.START);
302            }
303        } else {
304            // add location to end
305            rl = _route.addLocation(l);
306        }
307        rl.setTrainDirection(routeModel.getLastTrainDirection());
308        rl.setMaxTrainLength(routeModel.getLastMaxTrainLength());
309        if (rl.getLocation().isStaging()) {
310            rl.setMaxCarMoves(50);
311        } else {
312            rl.setMaxCarMoves(routeModel.getLastMaxTrainMoves());
313        }
314        // set train icon location
315        rl.setTrainIconCoordinates();
316    }
317
318    private void saveNewRoute() {
319        if (!checkName(Bundle.getMessage("add"))) {
320            return;
321        }
322        _route = routeManager.newRoute(routeNameTextField.getText());
323        routeModel.initTable(this, routeTable, _route);
324        enableButtons(true);
325        // assign route to a train?
326        if (_train != null) {
327            _train.setRoute(_route);
328        }
329        if (_route != null) {
330            _route.addPropertyChangeListener(this);
331        }
332        saveRoute();
333        loadToolMenu();
334        selectFirstLocationComboBox();
335    }
336
337    private boolean saveRoute() {
338        if (routeTable.isEditing()) {
339            log.debug("route table edit true");
340            routeTable.getCellEditor().stopCellEditing();
341        }
342        
343        if (!checkName(Bundle.getMessage("save")) || !checkTrainDirections()) {
344            return false;
345        }
346        _route.setName(routeNameTextField.getText());
347        _route.setComment(commentTextField.getText());
348
349        // save route file
350        OperationsXml.save();
351        return true;
352    }
353
354    /**
355     *
356     * @return true if name is length is okay
357     */
358    private boolean checkName(String s) {
359        if (routeNameTextField.getText().trim().isEmpty()) {
360            log.debug("Must enter a name for the route");
361            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("MustEnterName"),
362                    Bundle.getMessage("CanNotRoute", s),
363                    JmriJOptionPane.ERROR_MESSAGE);
364            return false;
365        }
366        if (routeNameTextField.getText().length() > Control.max_len_string_route_name) {
367            JmriJOptionPane.showMessageDialog(this,
368                    Bundle.getMessage("RouteNameLess",
369                            Control.max_len_string_route_name + 1),
370                    Bundle.getMessage("CanNotRoute", s),
371                    JmriJOptionPane.ERROR_MESSAGE);
372            return false;
373        }
374        return true;
375    }
376
377    /*
378     * Checks to see if user has disabled the saved train directions for this route.
379     */
380    private boolean checkTrainDirections() {
381        // get the valid train directions
382        List<String> directions = Setup.getTrainDirectionList();
383        for (RouteLocation rl : _route.getLocationsBySequenceList()) {
384            if (!directions.contains(rl.getTrainDirectionString())) {
385                JmriJOptionPane.showMessageDialog(this,
386                        Bundle.getMessage("RouteDirection", rl.getId()),
387                        Bundle.getMessage("RouteDirectionError",
388                                rl.getTrainDirectionString()),
389                        JmriJOptionPane.ERROR_MESSAGE);
390                return false;
391            }
392        }
393        return true;
394    }
395
396    private void reportRouteExists(String s) {
397        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ReportExists"),
398                Bundle.getMessage("CanNotRoute", s), JmriJOptionPane.ERROR_MESSAGE);
399    }
400
401    private void enableButtons(boolean enabled) {
402        toolMenu.setEnabled(enabled);
403        locationBox.setEnabled(enabled);
404        addLocationButton.setEnabled(enabled);
405        addLocAtTop.setEnabled(enabled);
406        addLocAtMiddle.setEnabled(enabled);
407        addLocAtBottom.setEnabled(enabled);
408        saveRouteButton.setEnabled(enabled);
409        deleteRouteButton.setEnabled(enabled);
410        routeTable.setEnabled(enabled);
411        showTravel.setEnabled(enabled);
412        showDepartTime.setEnabled(enabled);
413        // the inverse!
414        addRouteButton.setEnabled(!enabled);
415    }
416
417    private void selectFirstLocationComboBox() {
418        if (locationBox.getItemCount() > 1) {
419            locationBox.setSelectedIndex(1);
420        }
421    }
422
423    @Override
424    public void dispose() {
425        InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent(tpm -> {
426            tpm.stopPersisting(routeTable);
427        });
428        if (_route != null) {
429            _route.removePropertyChangeListener(this);
430        }
431        routeModel.dispose();
432        super.dispose();
433    }
434
435    private void updateComboBoxes() {
436        InstanceManager.getDefault(LocationManager.class).updateComboBox(locationBox);
437    }
438
439    // if the route has a departure time in the first location set the
440    // showDepartTime radio button
441    private void setTimeWaitRadioButtons() {
442        showTravel.setSelected(true);
443        if (_route != null) {
444            RouteLocation rl = _route.getDepartsRouteLocation();
445            if (rl != null && !rl.getDepartureTime().equals(RouteLocation.NONE)) {
446                showDepartTime.setSelected(true);
447            }
448            routeModel.setWait(showTravel.isSelected());
449        }
450    }
451
452    @Override
453    public void propertyChange(java.beans.PropertyChangeEvent e) {
454        if (Control.SHOW_PROPERTY) {
455            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(),
456                    e.getNewValue());
457        }
458        if (e.getPropertyName().equals(LocationManager.LISTLENGTH_CHANGED_PROPERTY)) {
459            updateComboBoxes();
460        }
461        if (e.getPropertyName().equals(Route.ROUTE_STATUS_CHANGED_PROPERTY)) {
462            enableButtons(!_route.getStatus().equals(Route.TRAIN_BUILT)); // do not allow user to modify a built train
463            addRouteButton.setEnabled(false); // override and disable
464        }
465    }
466
467    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RouteEditFrame.class);
468}