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