001package jmri.jmrit.beantable.routetable;
002
003import jmri.*;
004import jmri.swing.NamedBeanComboBox;
005import jmri.swing.RowSorterUtil;
006import jmri.util.AlphanumComparator;
007import jmri.util.FileUtil;
008import jmri.util.JmriJFrame;
009import jmri.util.StringUtil;
010import jmri.script.swing.ScriptFileChooser;
011import jmri.util.swing.JComboBoxUtil;
012
013import javax.annotation.Nonnull;
014import javax.swing.*;
015import javax.swing.border.Border;
016import javax.swing.table.TableColumn;
017import javax.swing.table.TableColumnModel;
018import javax.swing.table.TableRowSorter;
019import java.awt.*;
020import java.awt.event.ActionEvent;
021import java.util.ArrayList;
022import java.util.List;
023
024/**
025 * Base class for Add/Edit frame for the Route Table.
026 *
027 * Split from {@link jmri.jmrit.beantable.RouteTableAction}
028 *
029 * @author Dave Duchamp Copyright (C) 2004
030 * @author Bob Jacobsen Copyright (C) 2007
031 * @author Simon Reader Copyright (C) 2008
032 * @author Pete Cressman Copyright (C) 2009
033 * @author Egbert Broerse Copyright (C) 2016
034 * @author Paul Bender Copyright (C) 2020
035 */
036public abstract class AbstractRouteAddEditFrame extends JmriJFrame {
037
038    protected final RouteManager routeManager;
039
040    static final String[] COLUMN_NAMES = {Bundle.getMessage("ColumnSystemName"),
041            Bundle.getMessage("ColumnUserName"),
042            Bundle.getMessage("Include"),
043            Bundle.getMessage("ColumnLabelSetState")};
044    private static final String SET_TO_ACTIVE = Bundle.getMessage("Set") + " " + Bundle.getMessage("SensorStateActive");
045    private static final String SET_TO_INACTIVE = Bundle.getMessage("Set") + " " + Bundle.getMessage("SensorStateInactive");
046    static final String SET_TO_TOGGLE = Bundle.getMessage("Set") + " " + Bundle.getMessage("Toggle");
047    private static final String[] sensorInputModes = new String[]{
048            Bundle.getMessage("OnCondition") + " " + Bundle.getMessage("SensorStateActive"),
049            Bundle.getMessage("OnCondition") + " " + Bundle.getMessage("SensorStateInactive"),
050            Bundle.getMessage("OnConditionChange"),
051            "Veto " + Bundle.getMessage("WhenCondition") + " " + Bundle.getMessage("SensorStateActive"),
052            "Veto " + Bundle.getMessage("WhenCondition") + " " + Bundle.getMessage("SensorStateInactive")
053    };
054    private static final int[] sensorInputModeValues = new int[]{Route.ONACTIVE, Route.ONINACTIVE, Route.ONCHANGE,
055            Route.VETOACTIVE, Route.VETOINACTIVE};
056
057    // safe methods to set the above 4 static field values
058    private static final int[] turnoutInputModeValues = new int[]{Route.ONCLOSED, Route.ONTHROWN, Route.ONCHANGE,
059            Route.VETOCLOSED, Route.VETOTHROWN};
060
061    static int ROW_HEIGHT;
062    // This group will get runtime updates to system-specific contents at
063    // the start of buildModel() above.  This is done to prevent
064    // invoking the TurnoutManager at class construction time,
065    // when it hasn't been configured yet
066    static String SET_TO_CLOSED = Bundle.getMessage("Set") + " "
067            + Bundle.getMessage("TurnoutStateClosed");
068    static String SET_TO_THROWN = Bundle.getMessage("Set") + " "
069            + Bundle.getMessage("TurnoutStateThrown");
070    private static String[] turnoutInputModes = new String[]{
071            Bundle.getMessage("OnCondition") + " " + Bundle.getMessage("TurnoutStateClosed"),
072            Bundle.getMessage("OnCondition") + " " + Bundle.getMessage("TurnoutStateThrown"),
073            Bundle.getMessage("OnConditionChange"),
074            "Veto " + Bundle.getMessage("WhenCondition") + " " + Bundle.getMessage("TurnoutStateClosed"),
075            "Veto " + Bundle.getMessage("WhenCondition") + " " + Bundle.getMessage("TurnoutStateThrown")
076    };
077    private static String[] turnoutFeedbackModes = new String[]{Bundle.getMessage("TurnoutFeedbackKnown"),
078                                                                Bundle.getMessage("TurnoutFeedbackCommanded")};
079
080    private static String[] lockTurnoutInputModes = new String[]{
081            Bundle.getMessage("OnCondition") + " " + Bundle.getMessage("TurnoutStateClosed"),
082            Bundle.getMessage("OnCondition") + " " + Bundle.getMessage("TurnoutStateThrown"),
083            Bundle.getMessage("OnConditionChange")
084    };
085    final JTextField _systemName = new JTextField(10);
086    final JTextField _userName = new JTextField(22);
087    final JCheckBox _autoSystemName = new JCheckBox(Bundle.getMessage("LabelAutoSysName"));
088    final JTextField soundFile = new JTextField(20);
089    final JTextField scriptFile = new JTextField(20);
090    final JComboBox<String> sensor1mode = new JComboBox<>(sensorInputModes);
091    final JComboBox<String> sensor2mode = new JComboBox<>(sensorInputModes);
092    final JComboBox<String> sensor3mode = new JComboBox<>(sensorInputModes);
093    final JSpinner timeDelay = new JSpinner();
094    final JComboBox<String> cTurnoutStateBox = new JComboBox<>(turnoutInputModes);
095    final JComboBox<String> cTurnoutFeedbackBox = new JComboBox<>(turnoutFeedbackModes);
096    final JComboBox<String> cLockTurnoutStateBox = new JComboBox<>(lockTurnoutInputModes);
097    final JLabel nameLabel = new JLabel(Bundle.getMessage("LabelSystemName"));
098    final JLabel userLabel = new JLabel(Bundle.getMessage("LabelUserName"));
099    final JLabel status1 = new JLabel();
100    final JLabel status2 = new JLabel();
101
102    protected final String systemNameAuto = this.getClass().getName() + ".AutoSystemName";
103
104    ArrayList<RouteTurnout> _turnoutList;      // array of all Turnouts
105    ArrayList<RouteSensor> _sensorList;        // array of all Sensors
106    RouteTurnoutModel _routeTurnoutModel;
107    JScrollPane _routeTurnoutScrollPane;
108    RouteSensorModel _routeSensorModel;
109    JScrollPane _routeSensorScrollPane;
110    NamedBeanComboBox<Sensor> turnoutsAlignedSensor;
111    NamedBeanComboBox<Sensor> sensor1;
112    NamedBeanComboBox<Sensor> sensor2;
113    NamedBeanComboBox<Sensor> sensor3;
114    NamedBeanComboBox<Turnout> cTurnout;
115    NamedBeanComboBox<Turnout> cLockTurnout;
116    Route curRoute = null;
117    boolean editMode = false;
118    protected ArrayList<RouteTurnout> _includedTurnoutList;
119    protected ArrayList<RouteSensor> _includedSensorList;
120    protected UserPreferencesManager pref;
121    private JRadioButton allButton = null;
122    protected boolean routeDirty = false;  // true to fire reminder to save work
123    private boolean showAll = true;   // false indicates show only included Turnouts
124    private JFileChooser soundChooser = null;
125    private ScriptFileChooser scriptChooser = null;
126    private boolean checkEnabled = jmri.InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).isStoreCheckEnabled();
127
128    public AbstractRouteAddEditFrame(String name, boolean saveSize, boolean savePosition) {
129        super(name, saveSize, savePosition);
130
131        setClosedString(Bundle.getMessage("Set") + " "
132                + InstanceManager.turnoutManagerInstance().getClosedText());
133        setThrownString(Bundle.getMessage("Set") + " "
134                + InstanceManager.turnoutManagerInstance().getThrownText());
135        setTurnoutInputModes(new String[]{
136                Bundle.getMessage("OnCondition") + " " + InstanceManager.turnoutManagerInstance().getClosedText(),
137                Bundle.getMessage("OnCondition") + " " + InstanceManager.turnoutManagerInstance().getThrownText(),
138                Bundle.getMessage("OnConditionChange"),
139                "Veto " + Bundle.getMessage("WhenCondition") + " " + Bundle.getMessage("TurnoutStateClosed"),
140                "Veto " + Bundle.getMessage("WhenCondition") + " " + Bundle.getMessage("TurnoutStateThrown")
141        });
142        setLockTurnoutModes(new String[]{
143                Bundle.getMessage("OnCondition") + " " + InstanceManager.turnoutManagerInstance().getClosedText(),
144                Bundle.getMessage("OnCondition") + " " + InstanceManager.turnoutManagerInstance().getThrownText(),
145                Bundle.getMessage("OnConditionChange")
146        });
147
148        routeManager = InstanceManager.getDefault(RouteManager.class);
149
150    }
151
152    protected static void setClosedString(@Nonnull String newVal) {
153        SET_TO_CLOSED = newVal;
154    }
155
156    protected static void setThrownString(@Nonnull String newVal) {
157        SET_TO_THROWN = newVal;
158    }
159
160    protected static void setTurnoutInputModes(@Nonnull String[] newArray) {
161        turnoutInputModes = newArray;
162    }
163
164    protected static void setLockTurnoutModes(@Nonnull String[] newArray) {
165        lockTurnoutInputModes = newArray;
166    }
167
168    private static synchronized void setRowHeight(int newVal) {
169        ROW_HEIGHT = newVal;
170    }
171
172    @Override
173    public void initComponents() {
174        super.initComponents();
175
176        pref = InstanceManager.getDefault(UserPreferencesManager.class);
177        if (editMode) {
178            cancelEdit();
179        }
180        TurnoutManager tm = InstanceManager.turnoutManagerInstance();
181        _turnoutList = new ArrayList<>();
182        for (Turnout t : tm.getNamedBeanSet()) {
183            String systemName = t.getSystemName();
184            String userName = t.getUserName();
185            _turnoutList.add(new RouteTurnout(systemName, userName));
186        }
187
188        SensorManager sm = InstanceManager.sensorManagerInstance();
189        _sensorList = new ArrayList<>();
190        for (Sensor s : sm.getNamedBeanSet()) {
191            String systemName = s.getSystemName();
192            String userName = s.getUserName();
193            _sensorList.add(new RouteSensor(systemName, userName));
194        }
195        initializeIncludedList();
196
197        turnoutsAlignedSensor = new NamedBeanComboBox<>(InstanceManager.sensorManagerInstance());
198        sensor1 = new NamedBeanComboBox<>(InstanceManager.sensorManagerInstance());
199        sensor2 = new NamedBeanComboBox<>(InstanceManager.sensorManagerInstance());
200        sensor3 = new NamedBeanComboBox<>(InstanceManager.sensorManagerInstance());
201        cTurnout = new NamedBeanComboBox<>(InstanceManager.turnoutManagerInstance());
202        cLockTurnout = new NamedBeanComboBox<>(InstanceManager.turnoutManagerInstance());
203
204        // Set combo max rows
205        JComboBoxUtil.setupComboBoxMaxRows(turnoutsAlignedSensor);
206        JComboBoxUtil.setupComboBoxMaxRows(sensor1);
207        JComboBoxUtil.setupComboBoxMaxRows(sensor2);
208        JComboBoxUtil.setupComboBoxMaxRows(sensor3);
209        JComboBoxUtil.setupComboBoxMaxRows(cTurnout);
210        JComboBoxUtil.setupComboBoxMaxRows(cLockTurnout);
211
212        addHelpMenu("package.jmri.jmrit.beantable.RouteAddEdit", true);
213        setLocation(100, 30);
214
215        JPanel contentPanel = new JPanel();
216        contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
217        // add system name
218        JPanel ps = new JPanel();
219        ps.setLayout(new FlowLayout());
220        ps.add(nameLabel);
221        nameLabel.setLabelFor(_systemName);
222        ps.add(_systemName);
223        ps.add(_autoSystemName);
224        _autoSystemName.addActionListener((ActionEvent e1) -> autoSystemName());
225        if (pref.getSimplePreferenceState(systemNameAuto)) {
226            _autoSystemName.setSelected(true);
227            _systemName.setEnabled(false);
228        }
229        _systemName.setToolTipText(Bundle.getMessage("TooltipRouteSystemName"));
230        contentPanel.add(ps);
231        // add user name
232        JPanel p = new JPanel();
233        p.setLayout(new FlowLayout());
234        p.add(userLabel);
235        userLabel.setLabelFor(_userName);
236        p.add(_userName);
237        _userName.setToolTipText(Bundle.getMessage("TooltipRouteUserName"));
238        contentPanel.add(p);
239        // add Turnout Display Choice
240        JPanel py = new JPanel();
241        py.add(new JLabel(Bundle.getMessage("Show") + ":"));
242        ButtonGroup selGroup = new ButtonGroup();
243        allButton = new JRadioButton(Bundle.getMessage("All"), true);
244        selGroup.add(allButton);
245        py.add(allButton);
246        allButton.addActionListener((ActionEvent e1) -> {
247            // Setup for display of all Turnouts, if needed
248            if (!showAll) {
249                showAll = true;
250                _routeTurnoutModel.fireTableDataChanged();
251                _routeSensorModel.fireTableDataChanged();
252            }
253        });
254        JRadioButton includedButton = new JRadioButton(Bundle.getMessage("Included"), false);
255        selGroup.add(includedButton);
256        py.add(includedButton);
257        includedButton.addActionListener((ActionEvent e1) -> {
258            // Setup for display of included Turnouts only, if needed
259            if (showAll) {
260                showAll = false;
261                initializeIncludedList();
262                _routeTurnoutModel.fireTableDataChanged();
263                _routeSensorModel.fireTableDataChanged();
264            }
265        });
266        py.add(new JLabel(Bundle.getMessage("_and_", Bundle.getMessage("Turnouts"), Bundle.getMessage("Sensors"))));
267        // keys are in jmri.jmrit.Bundle
268        contentPanel.add(py);
269
270        contentPanel.add(getTurnoutPanel());
271        contentPanel.add(getSensorPanel());
272        contentPanel.add(getFileNamesPanel());
273        contentPanel.add(getAlignedSensorPanel());
274        contentPanel.add(getControlsPanel());
275        contentPanel.add(getLockPanel());
276        contentPanel.add(getNotesPanel());
277        contentPanel.add(getButtonPanel());
278
279        getContentPane().add(new JScrollPane(contentPanel), BorderLayout.CENTER);
280
281        pack();
282
283        // set listener for window closing
284        addWindowListener(new java.awt.event.WindowAdapter() {
285            @Override
286            public void windowClosing(java.awt.event.WindowEvent e) {
287                closeFrame();
288            }
289        });
290    }
291
292    protected abstract JPanel getButtonPanel();
293
294    private JPanel getNotesPanel() {
295        // add notes panel
296        JPanel pa = new JPanel();
297        pa.setLayout(new BoxLayout(pa, BoxLayout.Y_AXIS));
298        JPanel p1 = new JPanel();
299        p1.setLayout(new FlowLayout());
300        status1.setText(Bundle.getMessage("RouteAddStatusInitial1", Bundle.getMessage("ButtonCreate"))); // I18N to include original button name in help string
301        status1.setFont(status1.getFont().deriveFont(0.9f * nameLabel.getFont().getSize())); // a bit smaller
302        status1.setForeground(Color.gray);
303        p1.add(status1);
304        JPanel p2 = new JPanel();
305        p2.setLayout(new FlowLayout());
306        status2.setText(Bundle.getMessage("RouteAddStatusInitial5", Bundle.getMessage("ButtonCancel","")));
307        status2.setFont(status1.getFont().deriveFont(0.9f * nameLabel.getFont().getSize())); // a bit smaller
308        status2.setForeground(Color.gray);
309        p2.add(status2);
310        pa.add(p1);
311        pa.add(p2);
312        Border pBorder = BorderFactory.createEtchedBorder();
313        pa.setBorder(pBorder);
314        return pa;
315    }
316
317    private JPanel getLockPanel() {
318        // add lock control table
319        JPanel p4 = new JPanel();
320        p4.setLayout(new BoxLayout(p4, BoxLayout.Y_AXIS));
321        // add lock control turnout
322        JPanel p43 = new JPanel();
323        p43.add(new JLabel(Bundle.getMessage("LabelLockTurnout")));
324        p4.add(p43);
325        JPanel p44 = new JPanel();
326        p44.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameTurnout"))));
327        p44.add(cLockTurnout);
328        cLockTurnout.setAllowNull(true);
329        cLockTurnout.setSelectedItem(null);
330        cLockTurnout.setToolTipText(Bundle.getMessage("TooltipEnterTurnout"));
331        p44.add(new JLabel("   " + Bundle.getMessage("MakeLabel", Bundle.getMessage("LabelCondition"))));
332        cLockTurnoutStateBox.setToolTipText(Bundle.getMessage("TooltipLockTurnout"));
333        p44.add(cLockTurnoutStateBox);
334        p4.add(p44);
335        // complete this panel
336        Border p4Border = BorderFactory.createEtchedBorder();
337        p4.setBorder(p4Border);
338        return p4;
339    }
340
341    private JPanel getControlsPanel() {
342        // add Control Sensor table
343        JPanel p3 = new JPanel();
344        p3.setLayout(new BoxLayout(p3, BoxLayout.Y_AXIS));
345        JPanel p31 = new JPanel();
346        p31.add(new JLabel(Bundle.getMessage("LabelEnterSensors")));
347        p3.add(p31);
348        JPanel p32 = new JPanel();
349        //Sensor 1
350        JPanel pS = new JPanel();
351        pS.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("BeanNameSensor") + " 1"));
352        pS.add(sensor1);
353        pS.add(sensor1mode);
354        p32.add(pS);
355        //Sensor 2
356        pS = new JPanel();
357        pS.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("BeanNameSensor") + " 2"));
358        pS.add(sensor2);
359        pS.add(sensor2mode);
360        p32.add(pS);
361        //Sensor 3
362        pS = new JPanel();
363        pS.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("BeanNameSensor") + " 3"));
364        pS.add(sensor3);
365        pS.add(sensor3mode);
366        p32.add(pS);
367
368        sensor1.setAllowNull(true);
369        sensor2.setAllowNull(true);
370        sensor3.setAllowNull(true);
371        sensor1.setSelectedItem(null);
372        sensor2.setSelectedItem(null);
373        sensor3.setSelectedItem(null);
374        String sensorHint = Bundle.getMessage("TooltipEnterSensors");
375        sensor1.setToolTipText(sensorHint);
376        sensor2.setToolTipText(sensorHint);
377        sensor3.setToolTipText(sensorHint);
378        p3.add(p32);
379        // add control turnout
380        JPanel p33 = new JPanel();
381        p33.add(new JLabel(Bundle.getMessage("LabelEnterTurnout")));
382        p3.add(p33);
383        JPanel p34 = new JPanel();
384        p34.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameTurnout"))));
385        p34.add(cTurnout);
386        cTurnout.setAllowNull(true);
387        cTurnout.setSelectedItem(null);
388        cTurnout.setToolTipText(Bundle.getMessage("TooltipEnterTurnout"));
389        p34.add(new JLabel("   " + Bundle.getMessage("MakeLabel", Bundle.getMessage("LabelCondition"))));
390        cTurnoutStateBox.setToolTipText(Bundle.getMessage("TooltipTurnoutCondition"));
391        p34.add(cTurnoutStateBox);
392        p34.add(new JLabel(Bundle.getMessage("Is")));
393        cTurnoutFeedbackBox.setToolTipText(Bundle.getMessage("TooltipTurnoutFeedback"));
394        p34.add(cTurnoutFeedbackBox);
395        p3.add(p34);
396        // add additional route-specific delay
397        JPanel p36 = new JPanel();
398        p36.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("LabelTurnoutDelay"))));
399        timeDelay.setModel(new SpinnerNumberModel(0, 0, 1000, 1));
400        // timeDelay.setValue(0); // reset from possible previous use
401        timeDelay.setPreferredSize(new JTextField(5).getPreferredSize());
402        p36.add(timeDelay);
403        timeDelay.setToolTipText(Bundle.getMessage("TooltipTurnoutDelay"));
404        p36.add(new JLabel(Bundle.getMessage("LabelMilliseconds")));
405        p3.add(p36);
406        // complete this panel
407        Border p3Border = BorderFactory.createEtchedBorder();
408        p3.setBorder(p3Border);
409        return p3;
410    }
411
412    private JPanel getAlignedSensorPanel() {
413        //add turnouts aligned Sensor
414        JPanel p27 = new JPanel();
415        p27.setLayout(new FlowLayout());
416        p27.add(new JLabel(Bundle.getMessage("LabelEnterSensorAligned")));
417        p27.add(turnoutsAlignedSensor);
418        turnoutsAlignedSensor.setAllowNull(true);
419        turnoutsAlignedSensor.setSelectedItem(null);
420        turnoutsAlignedSensor.setToolTipText(Bundle.getMessage("TooltipEnterSensor"));
421        return p27;
422    }
423
424    private JPanel getFileNamesPanel() {
425        // Enter filenames for sound, script
426        JPanel p25 = new JPanel();
427        p25.setLayout(new FlowLayout());
428        p25.add(new JLabel(Bundle.getMessage("LabelPlaySound")));
429        p25.add(soundFile);
430        JButton ss = new JButton("..."); //NO18N
431        ss.addActionListener((ActionEvent e1) -> setSoundPressed());
432        ss.setToolTipText(Bundle.getMessage("TooltipOpenFile", Bundle.getMessage("BeanNameAudio")));
433        p25.add(ss);
434        p25.add(new JLabel(Bundle.getMessage("LabelRunScript")));
435        p25.add(scriptFile);
436        ss = new JButton("..."); //NO18N
437        ss.addActionListener((ActionEvent e1) -> setScriptPressed());
438        ss.setToolTipText(Bundle.getMessage("TooltipOpenFile", Bundle.getMessage("Script")));
439        p25.add(ss);
440        return p25;
441    }
442
443    private JPanel getTurnoutPanel(){
444        // add Turnout table
445        // Turnout list table
446        JPanel p2xt = new JPanel();
447        JPanel p2xtSpace = new JPanel();
448        p2xtSpace.setLayout(new BoxLayout(p2xtSpace, BoxLayout.Y_AXIS));
449        p2xtSpace.add(Box.createRigidArea(new Dimension(30,0)));
450        p2xt.add(p2xtSpace);
451
452        JPanel p21t = new JPanel();
453        p21t.setLayout(new BoxLayout(p21t, BoxLayout.Y_AXIS));
454        p21t.add(new JLabel(Bundle.getMessage("SelectInRoute", Bundle.getMessage("Turnouts"))));
455        p2xt.add(p21t);
456        _routeTurnoutModel = new RouteTurnoutModel(this);
457        JTable routeTurnoutTable = new JTable(_routeTurnoutModel);
458        TableRowSorter<RouteTurnoutModel> rtSorter = new TableRowSorter<>(_routeTurnoutModel);
459
460        // Use AlphanumComparator for SNAME and UNAME columns.  Start with SNAME sort.
461        rtSorter.setComparator(RouteOutputModel.SNAME_COLUMN, new AlphanumComparator());
462        rtSorter.setComparator(RouteOutputModel.UNAME_COLUMN, new AlphanumComparator());
463        RowSorterUtil.setSortOrder(rtSorter, RouteOutputModel.SNAME_COLUMN, SortOrder.ASCENDING);
464
465        routeTurnoutTable.setRowSorter(rtSorter);
466        routeTurnoutTable.setRowSelectionAllowed(false);
467        routeTurnoutTable.setPreferredScrollableViewportSize(new Dimension(480, 80));
468
469        setRowHeight(routeTurnoutTable.getRowHeight());
470        JComboBox<String> stateTCombo = new JComboBox<>();
471        stateTCombo.addItem(SET_TO_CLOSED);
472        stateTCombo.addItem(SET_TO_THROWN);
473        stateTCombo.addItem(SET_TO_TOGGLE);
474        TableColumnModel routeTurnoutColumnModel = routeTurnoutTable.getColumnModel();
475        TableColumn includeColumnT = routeTurnoutColumnModel.
476                getColumn(RouteOutputModel.INCLUDE_COLUMN);
477        includeColumnT.setResizable(false);
478        includeColumnT.setMinWidth(50);
479        includeColumnT.setMaxWidth(60);
480        TableColumn sNameColumnT = routeTurnoutColumnModel.
481                getColumn(RouteOutputModel.SNAME_COLUMN);
482        sNameColumnT.setResizable(true);
483        sNameColumnT.setMinWidth(75);
484        sNameColumnT.setMaxWidth(95);
485        TableColumn uNameColumnT = routeTurnoutColumnModel.
486                getColumn(RouteOutputModel.UNAME_COLUMN);
487        uNameColumnT.setResizable(true);
488        uNameColumnT.setMinWidth(210);
489        uNameColumnT.setMaxWidth(260);
490        TableColumn stateColumnT = routeTurnoutColumnModel.
491                getColumn(RouteOutputModel.STATE_COLUMN);
492        stateColumnT.setCellEditor(new DefaultCellEditor(stateTCombo));
493        stateColumnT.setResizable(false);
494        stateColumnT.setMinWidth(90);
495        stateColumnT.setMaxWidth(100);
496        _routeTurnoutScrollPane = new JScrollPane(routeTurnoutTable);
497        p2xt.add(_routeTurnoutScrollPane, BorderLayout.CENTER);
498        p2xt.setVisible(true);
499        return p2xt;
500    }
501
502    private JPanel getSensorPanel(){
503        // add Sensor table
504        // Sensor list table
505        JPanel p2xs = new JPanel();
506        JPanel p2xsSpace = new JPanel();
507        p2xsSpace.setLayout(new BoxLayout(p2xsSpace, BoxLayout.Y_AXIS));
508        p2xsSpace.add(Box.createRigidArea(new Dimension(30,0)));
509        p2xs.add(p2xsSpace);
510
511        JPanel p21s = new JPanel();
512        p21s.setLayout(new BoxLayout(p21s, BoxLayout.Y_AXIS));
513        p21s.add(new JLabel(Bundle.getMessage("SelectInRoute", Bundle.getMessage("Sensors"))));
514        p2xs.add(p21s);
515        _routeSensorModel = new RouteSensorModel(this);
516        JTable routeSensorTable = new JTable(_routeSensorModel);
517        TableRowSorter<RouteSensorModel> rsSorter = new TableRowSorter<>(_routeSensorModel);
518
519        // Use AlphanumComparator for SNAME and UNAME columns.  Start with SNAME sort.
520        rsSorter.setComparator(RouteOutputModel.SNAME_COLUMN, new AlphanumComparator());
521        rsSorter.setComparator(RouteOutputModel.UNAME_COLUMN, new AlphanumComparator());
522        RowSorterUtil.setSortOrder(rsSorter, RouteOutputModel.SNAME_COLUMN, SortOrder.ASCENDING);
523        routeSensorTable.setRowSorter(rsSorter);
524        routeSensorTable.setRowSelectionAllowed(false);
525        routeSensorTable.setPreferredScrollableViewportSize(new Dimension(480, 80));
526        JComboBox<String> stateSCombo = new JComboBox<>();
527        stateSCombo.addItem(SET_TO_ACTIVE);
528        stateSCombo.addItem(SET_TO_INACTIVE);
529        stateSCombo.addItem(SET_TO_TOGGLE);
530        TableColumnModel routeSensorColumnModel = routeSensorTable.getColumnModel();
531        TableColumn includeColumnS = routeSensorColumnModel.
532                getColumn(RouteOutputModel.INCLUDE_COLUMN);
533        includeColumnS.setResizable(false);
534        includeColumnS.setMinWidth(50);
535        includeColumnS.setMaxWidth(60);
536        TableColumn sNameColumnS = routeSensorColumnModel.
537                getColumn(RouteOutputModel.SNAME_COLUMN);
538        sNameColumnS.setResizable(true);
539        sNameColumnS.setMinWidth(75);
540        sNameColumnS.setMaxWidth(95);
541        TableColumn uNameColumnS = routeSensorColumnModel.
542                getColumn(RouteOutputModel.UNAME_COLUMN);
543        uNameColumnS.setResizable(true);
544        uNameColumnS.setMinWidth(210);
545        uNameColumnS.setMaxWidth(260);
546        TableColumn stateColumnS = routeSensorColumnModel.
547                getColumn(RouteOutputModel.STATE_COLUMN);
548        stateColumnS.setCellEditor(new DefaultCellEditor(stateSCombo));
549        stateColumnS.setResizable(false);
550        stateColumnS.setMinWidth(90);
551        stateColumnS.setMaxWidth(100);
552        _routeSensorScrollPane = new JScrollPane(routeSensorTable);
553        p2xs.add(_routeSensorScrollPane, BorderLayout.CENTER);
554        p2xs.setVisible(true);
555        return p2xs;
556    }
557
558    /**
559     * Initialize list of included turnout positions.
560     */
561    protected void initializeIncludedList() {
562        _includedTurnoutList = new ArrayList<>();
563        for (RouteTurnout routeTurnout : _turnoutList) {
564            if (routeTurnout.isIncluded()) {
565                _includedTurnoutList.add(routeTurnout);
566            }
567        }
568        _includedSensorList = new ArrayList<>();
569        for (RouteSensor routeSensor : _sensorList) {
570            if (routeSensor.isIncluded()) {
571                _includedSensorList.add(routeSensor);
572            }
573        }
574    }
575
576    private void autoSystemName() {
577        if (_autoSystemName.isSelected()) {
578            _systemName.setEnabled(false);
579            nameLabel.setEnabled(false);
580        } else {
581            _systemName.setEnabled(true);
582            nameLabel.setEnabled(true);
583        }
584    }
585
586    protected void showReminderMessage() {
587        // Use the RouteTabelAction class to combine messages in Preferences -> Messages
588        if (checkEnabled) return;
589        InstanceManager.getDefault(UserPreferencesManager.class).
590                showInfoMessage(Bundle.getMessage("ReminderTitle"),  // NOI18N
591                        Bundle.getMessage("ReminderSaveString", Bundle.getMessage("MenuItemRouteTable")),  // NOI18N
592                        jmri.jmrit.beantable.RouteTableAction.class.getName(), "remindSaveRoute"); // NOI18N
593    }
594
595    private int sensorModeFromBox(JComboBox<String> box) {
596        String mode = (String) box.getSelectedItem();
597        return sensorModeFromString(mode);
598    }
599
600    int sensorModeFromString(String mode) {
601        int result = StringUtil.getStateFromName(mode, sensorInputModeValues, sensorInputModes);
602
603        if (result < 0) {
604            log.warn("unexpected mode string in sensorMode: {}", mode);
605            throw new IllegalArgumentException();
606        }
607        return result;
608    }
609
610    void setSensorModeBox(int mode, JComboBox<String> box) {
611        String result = StringUtil.getNameFromState(mode, sensorInputModeValues, sensorInputModes);
612        box.setSelectedItem(result);
613    }
614
615    private int turnoutModeFromBox(JComboBox<String> box) {
616        String mode = (String) box.getSelectedItem();
617        int result = StringUtil.getStateFromName(mode, turnoutInputModeValues, turnoutInputModes);
618
619        if (result < 0) {
620            log.warn("unexpected mode string in turnoutMode: {}", mode);
621            throw new IllegalArgumentException();
622        }
623        return result;
624    }
625
626    void setTurnoutModeBox(int mode, JComboBox<String> box) {
627        String result = StringUtil.getNameFromState(mode, turnoutInputModeValues, turnoutInputModes);
628        box.setSelectedItem(result);
629    }
630
631    /**
632     * Set the Turnout information for adding or editing.
633     *
634     * @param g the route to add the turnout to
635     */
636    protected void setTurnoutInformation(Route g) {
637        for (RouteTurnout t : _includedTurnoutList) {
638            g.addOutputTurnout(t.getDisplayName(), t.getState());
639        }
640    }
641
642    /**
643     * Sets the Sensor information for adding or editing.
644     *
645     * @param g the route to add the sensor to
646     */
647    protected void setSensorInformation(Route g) {
648        for (RouteSensor s : _includedSensorList) {
649            g.addOutputSensor(s.getDisplayName(), s.getState());
650        }
651    }
652
653    /**
654     * Set the Sensor, Turnout, and delay control information for adding or editing.
655     *
656     * @param g the route to configure
657     */
658    protected void setControlInformation(Route g) {
659        // Get sensor control information if any
660        Sensor sensor = sensor1.getSelectedItem();
661        if (sensor != null) {
662            if ((!g.addSensorToRoute(sensor.getSystemName(), sensorModeFromBox(sensor1mode)))) {
663                log.error("Unexpected failure to add Sensor '{}' to route '{}'.", sensor.getSystemName(), g.getSystemName());
664            }
665        }
666
667        if (sensor2.getSelectedItem() != null) {
668            if ((!g.addSensorToRoute(sensor2.getSelectedItemDisplayName(), sensorModeFromBox(sensor2mode)))) {
669                log.error("Unexpected failure to add Sensor '{}' to Route '{}'.", sensor2.getSelectedItemDisplayName(), g.getSystemName());
670            }
671        }
672
673        if (sensor3.getSelectedItem() != null) {
674            if ((!g.addSensorToRoute(sensor3.getSelectedItemDisplayName(), sensorModeFromBox(sensor3mode)))) {
675                log.error("Unexpected failure to add Sensor '{}' to Route '{}'.", sensor3.getSelectedItemDisplayName(), g.getSystemName());
676            }
677        }
678
679        //Turnouts Aligned sensor
680        if (turnoutsAlignedSensor.getSelectedItem() != null) {
681            g.setTurnoutsAlignedSensor(turnoutsAlignedSensor.getSelectedItemDisplayName());
682        } else {
683            g.setTurnoutsAlignedSensor("");
684        }
685
686        // Set turnout information if there is any
687        if (cTurnout.getSelectedItem() != null) {
688            g.setControlTurnout(cTurnout.getSelectedItemDisplayName());
689            // set up Control Turnout state
690            g.setControlTurnoutState(turnoutModeFromBox(cTurnoutStateBox));
691            g.setControlTurnoutFeedback(cTurnoutFeedbackBox.getSelectedIndex() == 1);
692
693        } else {
694            // No Control Turnout was entered
695            g.setControlTurnout("");
696        }
697        // set route specific Delay information, see jmri.implementation.DefaultRoute#SetRouteThread()
698        int addDelay = (Integer) timeDelay.getValue(); // from a JSpinner with 0 set as minimum
699        g.setRouteCommandDelay(addDelay);
700
701        // Set Lock Turnout information if there is any
702        if (cLockTurnout.getSelectedItem() != null) {
703            g.setLockControlTurnout(cLockTurnout.getSelectedItemDisplayName());
704            // set up control turnout state
705            g.setLockControlTurnoutState(turnoutModeFromBox(cLockTurnoutStateBox));
706        } else {
707            // No Lock Turnout was entered
708            g.setLockControlTurnout("");
709        }
710    }
711
712    /**
713     * Set the sound file.
714     */
715    private void setSoundPressed() {
716        if (soundChooser == null) {
717            soundChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
718            soundChooser.setFileFilter(new jmri.util.NoArchiveFileFilter());
719        }
720        soundChooser.rescanCurrentDirectory();
721        int retVal = soundChooser.showOpenDialog(null);
722        // handle selection or cancel
723        if (retVal == JFileChooser.APPROVE_OPTION) {
724            try {
725                soundFile.setText(soundChooser.getSelectedFile().getCanonicalPath());
726            } catch (java.io.IOException e) {
727                log.error("exception setting sound file: ", e);
728            }
729        }
730    }
731
732    /**
733     * Set the script file.
734     */
735    private void setScriptPressed() {
736        if (scriptChooser == null) {
737            scriptChooser = new ScriptFileChooser();
738        }
739        scriptChooser.rescanCurrentDirectory();
740        int retVal = scriptChooser.showOpenDialog(null);
741        // handle selection or cancel
742        if (retVal == JFileChooser.APPROVE_OPTION) {
743            try {
744                scriptFile.setText(scriptChooser.getSelectedFile().getCanonicalPath());
745            } catch (java.io.IOException e) {
746                log.error("exception setting script file: ", e);
747            }
748        }
749    }
750
751
752    protected void finishUpdate() {
753        // move to show all Turnouts if not there
754        cancelIncludedOnly();
755        // Provide feedback to user
756        // switch GUI back to selection mode
757        //status2.setText(Bundle.getMessage("RouteAddStatusInitial2", Bundle.getMessage("ButtonEdit")));
758        status2.setVisible(true);
759        autoSystemName();
760        setTitle(Bundle.getMessage("TitleAddRoute"));
761        clearPage();
762        // reactivate the Route
763        routeDirty = true;
764        // get out of edit mode
765        editMode = false;
766        if (curRoute != null) {
767            curRoute.activateRoute();
768        }
769    }
770
771    /**
772     * Populate the page fields.  The route names are not included since they are handled
773     * by the Edit or Add actions.
774     * <p>
775     * The route is either the route being edited or a source route for doing a copy during
776     * the add action.
777     *
778     * @param route The route that contains the content.
779     */
780    protected void setPageContent(Route route) {
781        // set up Turnout list for this route
782        int setRow = 0;
783        for (int i = _turnoutList.size() - 1; i >= 0; i--) {
784            RouteTurnout turnout = _turnoutList.get(i);
785            String tSysName = turnout.getSysName();
786            if (route.isOutputTurnoutIncluded(tSysName)) {
787                turnout.setIncluded(true);
788                turnout.setState(route.getOutputTurnoutSetState(tSysName));
789                setRow = i;
790            } else {
791                turnout.setIncluded(false);
792                turnout.setState(Turnout.CLOSED);
793            }
794        }
795        setRow -= 1;
796        if (setRow < 0) {
797            setRow = 0;
798        }
799        _routeTurnoutScrollPane.getVerticalScrollBar().setValue(setRow * ROW_HEIGHT);
800        _routeTurnoutModel.fireTableDataChanged();
801
802        // set up Sensor list for this route
803        for (int i = _sensorList.size() - 1; i >= 0; i--) {
804            RouteSensor sensor = _sensorList.get(i);
805            String tSysName = sensor.getSysName();
806            if (route.isOutputSensorIncluded(tSysName)) {
807                sensor.setIncluded(true);
808                sensor.setState(route.getOutputSensorSetState(tSysName));
809                setRow = i;
810            } else {
811                sensor.setIncluded(false);
812                sensor.setState(Sensor.INACTIVE);
813            }
814        }
815        setRow -= 1;
816        if (setRow < 0) {
817            setRow = 0;
818        }
819        _routeSensorScrollPane.getVerticalScrollBar().setValue(setRow * ROW_HEIGHT);
820        _routeSensorModel.fireTableDataChanged();
821
822        // get Sound and  Script file names
823        scriptFile.setText(route.getOutputScriptName());
824        soundFile.setText(route.getOutputSoundName());
825
826        // get Turnout Aligned sensor
827        turnoutsAlignedSensor.setSelectedItem(route.getTurnoutsAlgdSensor());
828
829        // set up Control Sensors if there are any
830        Sensor[] temNames = new Sensor[Route.MAX_CONTROL_SENSORS];
831        int[] temModes = new int[Route.MAX_CONTROL_SENSORS];
832        for (int k = 0; k < Route.MAX_CONTROL_SENSORS; k++) {
833            temNames[k] = route.getRouteSensor(k);
834            temModes[k] = route.getRouteSensorMode(k);
835        }
836        sensor1.setSelectedItem(temNames[0]);
837        setSensorModeBox(temModes[0], sensor1mode);
838
839        sensor2.setSelectedItem(temNames[1]);
840        setSensorModeBox(temModes[1], sensor2mode);
841
842        sensor3.setSelectedItem(temNames[2]);
843        setSensorModeBox(temModes[2], sensor3mode);
844
845        // set up Control Turnout if there is one
846        cTurnout.setSelectedItem(route.getCtlTurnout());
847
848        setTurnoutModeBox(route.getControlTurnoutState(), cTurnoutStateBox);
849
850        if (route.getControlTurnoutFeedback()) {
851            cTurnoutFeedbackBox.setSelectedIndex(1); // Known
852        } else {
853            cTurnoutFeedbackBox.setSelectedIndex(0);  // Commanded
854        }
855
856        // set up Lock Control Turnout if there is one
857        cLockTurnout.setSelectedItem(route.getLockCtlTurnout());
858
859        setTurnoutModeBox(route.getLockControlTurnoutState(), cLockTurnoutStateBox);
860
861        // set up additional route specific Delay
862        timeDelay.setValue(route.getRouteCommandDelay());
863    }
864
865    private void clearPage() {
866        _systemName.setText("");
867        _userName.setText("");
868        sensor1.setSelectedItem(null);
869        sensor2.setSelectedItem(null);
870        sensor3.setSelectedItem(null);
871        cTurnout.setSelectedItem(null);
872        cLockTurnout.setSelectedItem(null);
873        turnoutsAlignedSensor.setSelectedItem(null);
874        soundFile.setText("");
875        scriptFile.setText("");
876        for (int i = _turnoutList.size() - 1; i >= 0; i--) {
877            _turnoutList.get(i).setIncluded(false);
878        }
879        for (int i = _sensorList.size() - 1; i >= 0; i--) {
880            _sensorList.get(i).setIncluded(false);
881        }
882    }
883
884
885    /**
886     * Cancel included Turnouts only option
887     */
888    private void cancelIncludedOnly() {
889        if (!showAll) {
890            allButton.doClick();
891        }
892    }
893
894    List<RouteTurnout> get_turnoutList() {
895        return _turnoutList;
896    }
897
898    List<RouteTurnout> get_includedTurnoutList() {
899        return _includedTurnoutList;
900    }
901
902    List<RouteSensor> get_sensorList() {
903        return _sensorList;
904    }
905
906    List<RouteSensor> get_includedSensorList() {
907        return _includedSensorList;
908    }
909
910    public boolean isShowAll() {
911        return showAll;
912    }
913
914    /**
915     * Cancels edit mode
916     */
917    protected void cancelEdit() {
918        if (editMode) {
919            status1.setText(Bundle.getMessage("RouteAddStatusInitial1", Bundle.getMessage("ButtonCreate"))); // I18N to include original button name in help string
920            //status2.setText(Bundle.getMessage("RouteAddStatusInitial2", Bundle.getMessage("ButtonEdit")));
921            finishUpdate();
922            // get out of edit mode
923            editMode = false;
924            curRoute = null;
925        }
926        closeFrame();
927    }
928
929    /**
930     * Respond to the Update button - update to Route Table.
931     *
932     * @param newRoute true if a new route; false otherwise
933     */
934    protected void updatePressed(boolean newRoute) {
935        // Check if the User Name has been changed
936        String uName = _userName.getText();
937        Route g = checkNamesOK();
938        if (g == null) {
939            return;
940        }
941        // User Name is unique, change it
942        g.setUserName(uName);
943        // clear the current Turnout information for this Route
944        g.clearOutputTurnouts();
945        g.clearOutputSensors();
946        // clear the current Sensor information for this Route
947        g.clearRouteSensors();
948        // add those indicated in the panel
949        initializeIncludedList();
950        setTurnoutInformation(g);
951        setSensorInformation(g);
952        // set the current values of the file names
953        g.setOutputScriptName(scriptFile.getText());
954        g.setOutputSoundName(soundFile.getText());
955        // add Control Sensors and a Control Turnout if entered in the panel
956        setControlInformation(g);
957        curRoute = g;
958        finishUpdate();
959        status1.setForeground(Color.gray);
960        status1.setText((newRoute ? Bundle.getMessage("RouteAddStatusCreated") :
961                Bundle.getMessage("RouteAddStatusUpdated")) + ": \"" + uName + "\" (" + _includedTurnoutList.size() + " "
962                + Bundle.getMessage("Turnouts") + ", " + _includedSensorList.size() + " " + Bundle.getMessage("Sensors") + ")");
963
964        closeFrame();
965    }
966
967    /**
968     * Check name and return a new or existing Route object with the name as entered in the _systemName field on the
969     * addFrame pane.
970     *
971     * @return the new/updated Route object
972     */
973    private Route checkNamesOK() {
974        // Get system name and user name
975        String sName = _systemName.getText();
976        String uName = _userName.getText();
977        Route g;
978        if (_autoSystemName.isSelected() && !editMode) {
979            log.debug("checkNamesOK new autogroup");
980            // create new Route with auto system name
981            g = routeManager.newRoute(uName);
982        } else {
983            if (sName.length() == 0) {
984                status1.setText(Bundle.getMessage("AddBeanStatusEnter"));
985                status1.setForeground(Color.red);
986                return null;
987            }
988            try {
989                sName = routeManager.makeSystemName(sName);
990                g = routeManager.provideRoute(sName, uName);
991            } catch (IllegalArgumentException ex) {
992                g = null; // for later check:
993            }
994        }
995        if (g == null) {
996            // should never get here
997            log.error("Unknown failure to create Route with System Name: {}", sName); // NOI18N
998        } else {
999            g.deActivateRoute();
1000        }
1001        return g;
1002    }
1003
1004    protected void closeFrame(){
1005        // remind to save, if Route was created or edited
1006        if (routeDirty) {
1007            showReminderMessage();
1008            routeDirty = false;
1009        }
1010        // hide addFrame
1011        setVisible(false);
1012
1013        // if in Edit, cancel edit mode
1014        if (editMode) {
1015            cancelEdit();
1016        }
1017        _routeSensorModel.dispose();
1018        _routeTurnoutModel.dispose();
1019        this.dispose();
1020    }
1021
1022    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractRouteAddEditFrame.class);
1023}