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        contentPanel.add(getButtonPanel());
281
282        getContentPane().add(new JScrollPane(contentPanel), BorderLayout.CENTER);
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(new Dimension(480, 80));
471
472        setRowHeight(routeTurnoutTable.getRowHeight());
473        JComboBox<String> stateTCombo = new JComboBox<>();
474        stateTCombo.addItem(SET_TO_CLOSED);
475        stateTCombo.addItem(SET_TO_THROWN);
476        stateTCombo.addItem(SET_TO_TOGGLE);
477        TableColumnModel routeTurnoutColumnModel = routeTurnoutTable.getColumnModel();
478        TableColumn includeColumnT = routeTurnoutColumnModel.
479                getColumn(RouteOutputModel.INCLUDE_COLUMN);
480        includeColumnT.setResizable(false);
481        includeColumnT.setMinWidth(50);
482        includeColumnT.setMaxWidth(60);
483        TableColumn sNameColumnT = routeTurnoutColumnModel.
484                getColumn(RouteOutputModel.SNAME_COLUMN);
485        sNameColumnT.setResizable(true);
486        sNameColumnT.setMinWidth(75);
487        sNameColumnT.setMaxWidth(95);
488        TableColumn uNameColumnT = routeTurnoutColumnModel.
489                getColumn(RouteOutputModel.UNAME_COLUMN);
490        uNameColumnT.setResizable(true);
491        uNameColumnT.setMinWidth(210);
492        uNameColumnT.setMaxWidth(260);
493        TableColumn stateColumnT = routeTurnoutColumnModel.
494                getColumn(RouteOutputModel.STATE_COLUMN);
495        stateColumnT.setCellEditor(new DefaultCellEditor(stateTCombo));
496        stateColumnT.setResizable(false);
497        stateColumnT.setMinWidth(90);
498        stateColumnT.setMaxWidth(100);
499        _routeTurnoutScrollPane = new JScrollPane(routeTurnoutTable);
500        p2xt.add(_routeTurnoutScrollPane, BorderLayout.CENTER);
501        p2xt.setVisible(true);
502        return p2xt;
503    }
504
505    private JPanel getSensorPanel(){
506        // add Sensor table
507        // Sensor list table
508        JPanel p2xs = new JPanel();
509        JPanel p2xsSpace = new JPanel();
510        p2xsSpace.setLayout(new BoxLayout(p2xsSpace, BoxLayout.Y_AXIS));
511        p2xsSpace.add(Box.createRigidArea(new Dimension(30,0)));
512        p2xs.add(p2xsSpace);
513
514        JPanel p21s = new JPanel();
515        p21s.setLayout(new BoxLayout(p21s, BoxLayout.Y_AXIS));
516        p21s.add(new JLabel(Bundle.getMessage("SelectInRoute", Bundle.getMessage("Sensors"))));
517        p2xs.add(p21s);
518        _routeSensorModel = new RouteSensorModel(this);
519        JTable routeSensorTable = new JTable(_routeSensorModel);
520        TableRowSorter<RouteSensorModel> rsSorter = new TableRowSorter<>(_routeSensorModel);
521
522        // Use AlphanumComparator for SNAME and UNAME columns.  Start with SNAME sort.
523        rsSorter.setComparator(RouteOutputModel.SNAME_COLUMN, new AlphanumComparator());
524        rsSorter.setComparator(RouteOutputModel.UNAME_COLUMN, new AlphanumComparator());
525        RowSorterUtil.setSortOrder(rsSorter, RouteOutputModel.SNAME_COLUMN, SortOrder.ASCENDING);
526        routeSensorTable.setRowSorter(rsSorter);
527        routeSensorTable.setRowSelectionAllowed(false);
528        routeSensorTable.setPreferredScrollableViewportSize(new Dimension(480, 80));
529        JComboBox<String> stateSCombo = new JComboBox<>();
530        stateSCombo.addItem(SET_TO_ACTIVE);
531        stateSCombo.addItem(SET_TO_INACTIVE);
532        stateSCombo.addItem(SET_TO_TOGGLE);
533        TableColumnModel routeSensorColumnModel = routeSensorTable.getColumnModel();
534        TableColumn includeColumnS = routeSensorColumnModel.
535                getColumn(RouteOutputModel.INCLUDE_COLUMN);
536        includeColumnS.setResizable(false);
537        includeColumnS.setMinWidth(50);
538        includeColumnS.setMaxWidth(60);
539        TableColumn sNameColumnS = routeSensorColumnModel.
540                getColumn(RouteOutputModel.SNAME_COLUMN);
541        sNameColumnS.setResizable(true);
542        sNameColumnS.setMinWidth(75);
543        sNameColumnS.setMaxWidth(95);
544        TableColumn uNameColumnS = routeSensorColumnModel.
545                getColumn(RouteOutputModel.UNAME_COLUMN);
546        uNameColumnS.setResizable(true);
547        uNameColumnS.setMinWidth(210);
548        uNameColumnS.setMaxWidth(260);
549        TableColumn stateColumnS = routeSensorColumnModel.
550                getColumn(RouteOutputModel.STATE_COLUMN);
551        stateColumnS.setCellEditor(new DefaultCellEditor(stateSCombo));
552        stateColumnS.setResizable(false);
553        stateColumnS.setMinWidth(90);
554        stateColumnS.setMaxWidth(100);
555        _routeSensorScrollPane = new JScrollPane(routeSensorTable);
556        p2xs.add(_routeSensorScrollPane, BorderLayout.CENTER);
557        p2xs.setVisible(true);
558        return p2xs;
559    }
560
561    /**
562     * Initialize list of included turnout positions.
563     */
564    protected void initializeIncludedList() {
565        _includedTurnoutList = new ArrayList<>();
566        for (RouteTurnout routeTurnout : _turnoutList) {
567            if (routeTurnout.isIncluded()) {
568                _includedTurnoutList.add(routeTurnout);
569            }
570        }
571        _includedSensorList = new ArrayList<>();
572        for (RouteSensor routeSensor : _sensorList) {
573            if (routeSensor.isIncluded()) {
574                _includedSensorList.add(routeSensor);
575            }
576        }
577    }
578
579    private void autoSystemName() {
580        if (_autoSystemName.isSelected()) {
581            _systemName.setEnabled(false);
582            nameLabel.setEnabled(false);
583        } else {
584            _systemName.setEnabled(true);
585            nameLabel.setEnabled(true);
586        }
587    }
588
589    protected void showReminderMessage() {
590        // Use the RouteTabelAction class to combine messages in Preferences -> Messages
591        if (checkEnabled) {
592            return;
593        }
594        InstanceManager.getDefault(UserPreferencesManager.class).
595                showInfoMessage(this, Bundle.getMessage("ReminderTitle"),  // NOI18N
596                        Bundle.getMessage("ReminderSaveString", Bundle.getMessage("MenuItemRouteTable")),  // NOI18N
597                        jmri.jmrit.beantable.RouteTableAction.class.getName(), "remindSaveRoute"); // NOI18N
598    }
599
600    private int sensorModeFromBox(JComboBox<String> box) {
601        String mode = (String) box.getSelectedItem();
602        return sensorModeFromString(mode);
603    }
604
605    int sensorModeFromString(String mode) {
606        int result = StringUtil.getStateFromName(mode, sensorInputModeValues, sensorInputModes);
607
608        if (result < 0) {
609            log.warn("unexpected mode string in sensorMode: {}", mode);
610            throw new IllegalArgumentException();
611        }
612        return result;
613    }
614
615    void setSensorModeBox(int mode, JComboBox<String> box) {
616        String result = StringUtil.getNameFromState(mode, sensorInputModeValues, sensorInputModes);
617        box.setSelectedItem(result);
618    }
619
620    private int turnoutModeFromBox(JComboBox<String> box) {
621        String mode = (String) box.getSelectedItem();
622        int result = StringUtil.getStateFromName(mode, turnoutInputModeValues, turnoutInputModes);
623
624        if (result < 0) {
625            log.warn("unexpected mode string in turnoutMode: {}", mode);
626            throw new IllegalArgumentException();
627        }
628        return result;
629    }
630
631    void setTurnoutModeBox(int mode, JComboBox<String> box) {
632        String result = StringUtil.getNameFromState(mode, turnoutInputModeValues, turnoutInputModes);
633        box.setSelectedItem(result);
634    }
635
636    /**
637     * Set the Turnout information for adding or editing.
638     *
639     * @param g the route to add the turnout to
640     */
641    protected void setTurnoutInformation(Route g) {
642        for (RouteTurnout t : _includedTurnoutList) {
643            g.addOutputTurnout(t.getDisplayName(), t.getState());
644        }
645    }
646
647    /**
648     * Sets the Sensor information for adding or editing.
649     *
650     * @param g the route to add the sensor to
651     */
652    protected void setSensorInformation(Route g) {
653        for (RouteSensor s : _includedSensorList) {
654            g.addOutputSensor(s.getDisplayName(), s.getState());
655        }
656    }
657
658    /**
659     * Set the Sensor, Turnout, and delay control information for adding or editing.
660     *
661     * @param g the route to configure
662     */
663    protected void setControlInformation(Route g) {
664        // Get sensor control information if any
665        Sensor sensor = sensor1.getSelectedItem();
666        if (sensor != null) {
667            if ((!g.addSensorToRoute(sensor.getSystemName(), sensorModeFromBox(sensor1mode)))) {
668                log.error("Unexpected failure to add Sensor '{}' to route '{}'.", sensor.getSystemName(), g.getSystemName());
669            }
670        }
671
672        if (sensor2.getSelectedItem() != null) {
673            if ((!g.addSensorToRoute(sensor2.getSelectedItemDisplayName(), sensorModeFromBox(sensor2mode)))) {
674                log.error("Unexpected failure to add Sensor '{}' to Route '{}'.", sensor2.getSelectedItemDisplayName(), g.getSystemName());
675            }
676        }
677
678        if (sensor3.getSelectedItem() != null) {
679            if ((!g.addSensorToRoute(sensor3.getSelectedItemDisplayName(), sensorModeFromBox(sensor3mode)))) {
680                log.error("Unexpected failure to add Sensor '{}' to Route '{}'.", sensor3.getSelectedItemDisplayName(), g.getSystemName());
681            }
682        }
683
684        //Turnouts Aligned sensor
685        if (turnoutsAlignedSensor.getSelectedItem() != null) {
686            g.setTurnoutsAlignedSensor(turnoutsAlignedSensor.getSelectedItemDisplayName());
687        } else {
688            g.setTurnoutsAlignedSensor("");
689        }
690
691        // Set turnout information if there is any
692        if (cTurnout.getSelectedItem() != null) {
693            g.setControlTurnout(cTurnout.getSelectedItemDisplayName());
694            // set up Control Turnout state
695            g.setControlTurnoutState(turnoutModeFromBox(cTurnoutStateBox));
696            g.setControlTurnoutFeedback(cTurnoutFeedbackBox.getSelectedIndex() == 1);
697
698        } else {
699            // No Control Turnout was entered
700            g.setControlTurnout("");
701        }
702        // set route specific Delay information, see jmri.implementation.DefaultRoute#SetRouteThread()
703        int addDelay = (Integer) timeDelay.getValue(); // from a JSpinner with 0 set as minimum
704        g.setRouteCommandDelay(addDelay);
705
706        // Set Lock Turnout information if there is any
707        if (cLockTurnout.getSelectedItem() != null) {
708            g.setLockControlTurnout(cLockTurnout.getSelectedItemDisplayName());
709            // set up control turnout state
710            g.setLockControlTurnoutState(turnoutModeFromBox(cLockTurnoutStateBox));
711        } else {
712            // No Lock Turnout was entered
713            g.setLockControlTurnout("");
714        }
715    }
716
717    /**
718     * Set the sound file.
719     */
720    private void setSoundPressed() {
721        if (soundChooser == null) {
722            soundChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
723            soundChooser.setFileFilter(new jmri.util.NoArchiveFileFilter());
724        }
725        soundChooser.rescanCurrentDirectory();
726        int retVal = soundChooser.showOpenDialog(null);
727        // handle selection or cancel
728        if (retVal == JFileChooser.APPROVE_OPTION) {
729            try {
730                soundFile.setText(soundChooser.getSelectedFile().getCanonicalPath());
731            } catch (java.io.IOException e) {
732                log.error("exception setting sound file: ", e);
733            }
734        }
735    }
736
737    /**
738     * Set the script file.
739     */
740    private void setScriptPressed() {
741        if (scriptChooser == null) {
742            scriptChooser = new ScriptFileChooser();
743        }
744        scriptChooser.rescanCurrentDirectory();
745        int retVal = scriptChooser.showOpenDialog(null);
746        // handle selection or cancel
747        if (retVal == JFileChooser.APPROVE_OPTION) {
748            try {
749                scriptFile.setText(scriptChooser.getSelectedFile().getCanonicalPath());
750            } catch (java.io.IOException e) {
751                log.error("exception setting script file: ", e);
752            }
753        }
754    }
755
756
757    protected void finishUpdate() {
758        // move to show all Turnouts if not there
759        cancelIncludedOnly();
760        // Provide feedback to user
761        // switch GUI back to selection mode
762        //status2.setText(Bundle.getMessage("RouteAddStatusInitial2", Bundle.getMessage("ButtonEdit")));
763        status2.setVisible(true);
764        autoSystemName();
765        setTitle(Bundle.getMessage("TitleAddRoute"));
766        clearPage();
767        // reactivate the Route
768        routeDirty = true;
769        // get out of edit mode
770        editMode = false;
771        if (curRoute != null) {
772            curRoute.activateRoute();
773        }
774    }
775
776    /**
777     * Populate the page fields.  The route names are not included since they are handled
778     * by the Edit or Add actions.
779     * <p>
780     * The route is either the route being edited or a source route for doing a copy during
781     * the add action.
782     *
783     * @param route The route that contains the content.
784     */
785    protected void setPageContent(Route route) {
786        // set up Turnout list for this route
787        int setRow = 0;
788        for (int i = _turnoutList.size() - 1; i >= 0; i--) {
789            RouteTurnout turnout = _turnoutList.get(i);
790            String tSysName = turnout.getSysName();
791            if (route.isOutputTurnoutIncluded(tSysName)) {
792                turnout.setIncluded(true);
793                turnout.setState(route.getOutputTurnoutSetState(tSysName));
794                setRow = i;
795            } else {
796                turnout.setIncluded(false);
797                turnout.setState(Turnout.CLOSED);
798            }
799        }
800        setRow -= 1;
801        if (setRow < 0) {
802            setRow = 0;
803        }
804        _routeTurnoutScrollPane.getVerticalScrollBar().setValue(setRow * ROW_HEIGHT);
805        _routeTurnoutModel.fireTableDataChanged();
806
807        // set up Sensor list for this route
808        for (int i = _sensorList.size() - 1; i >= 0; i--) {
809            RouteSensor sensor = _sensorList.get(i);
810            String tSysName = sensor.getSysName();
811            if (route.isOutputSensorIncluded(tSysName)) {
812                sensor.setIncluded(true);
813                sensor.setState(route.getOutputSensorSetState(tSysName));
814                setRow = i;
815            } else {
816                sensor.setIncluded(false);
817                sensor.setState(Sensor.INACTIVE);
818            }
819        }
820        setRow -= 1;
821        if (setRow < 0) {
822            setRow = 0;
823        }
824        _routeSensorScrollPane.getVerticalScrollBar().setValue(setRow * ROW_HEIGHT);
825        _routeSensorModel.fireTableDataChanged();
826
827        // get Sound and  Script file names
828        scriptFile.setText(route.getOutputScriptName());
829        soundFile.setText(route.getOutputSoundName());
830
831        // get Turnout Aligned sensor
832        turnoutsAlignedSensor.setSelectedItem(route.getTurnoutsAlgdSensor());
833
834        // set up Control Sensors if there are any
835        Sensor[] temNames = new Sensor[Route.MAX_CONTROL_SENSORS];
836        int[] temModes = new int[Route.MAX_CONTROL_SENSORS];
837        for (int k = 0; k < Route.MAX_CONTROL_SENSORS; k++) {
838            temNames[k] = route.getRouteSensor(k);
839            temModes[k] = route.getRouteSensorMode(k);
840        }
841        sensor1.setSelectedItem(temNames[0]);
842        setSensorModeBox(temModes[0], sensor1mode);
843
844        sensor2.setSelectedItem(temNames[1]);
845        setSensorModeBox(temModes[1], sensor2mode);
846
847        sensor3.setSelectedItem(temNames[2]);
848        setSensorModeBox(temModes[2], sensor3mode);
849
850        // set up Control Turnout if there is one
851        cTurnout.setSelectedItem(route.getCtlTurnout());
852
853        setTurnoutModeBox(route.getControlTurnoutState(), cTurnoutStateBox);
854
855        if (route.getControlTurnoutFeedback()) {
856            cTurnoutFeedbackBox.setSelectedIndex(1); // Known
857        } else {
858            cTurnoutFeedbackBox.setSelectedIndex(0);  // Commanded
859        }
860
861        // set up Lock Control Turnout if there is one
862        cLockTurnout.setSelectedItem(route.getLockCtlTurnout());
863
864        setTurnoutModeBox(route.getLockControlTurnoutState(), cLockTurnoutStateBox);
865
866        // set up additional route specific Delay
867        timeDelay.setValue(route.getRouteCommandDelay());
868    }
869
870    private void clearPage() {
871        _systemName.setText("");
872        _userName.setText("");
873        sensor1.setSelectedItem(null);
874        sensor2.setSelectedItem(null);
875        sensor3.setSelectedItem(null);
876        cTurnout.setSelectedItem(null);
877        cLockTurnout.setSelectedItem(null);
878        turnoutsAlignedSensor.setSelectedItem(null);
879        soundFile.setText("");
880        scriptFile.setText("");
881        for (int i = _turnoutList.size() - 1; i >= 0; i--) {
882            _turnoutList.get(i).setIncluded(false);
883        }
884        for (int i = _sensorList.size() - 1; i >= 0; i--) {
885            _sensorList.get(i).setIncluded(false);
886        }
887    }
888
889
890    /**
891     * Cancel included Turnouts only option
892     */
893    private void cancelIncludedOnly() {
894        if (!showAll) {
895            allButton.doClick();
896        }
897    }
898
899    List<RouteTurnout> get_turnoutList() {
900        return _turnoutList;
901    }
902
903    List<RouteTurnout> get_includedTurnoutList() {
904        return _includedTurnoutList;
905    }
906
907    List<RouteSensor> get_sensorList() {
908        return _sensorList;
909    }
910
911    List<RouteSensor> get_includedSensorList() {
912        return _includedSensorList;
913    }
914
915    public boolean isShowAll() {
916        return showAll;
917    }
918
919    /**
920     * Cancels edit mode
921     */
922    protected void cancelEdit() {
923        if (editMode) {
924            status1.setText(Bundle.getMessage("RouteAddStatusInitial1", Bundle.getMessage("ButtonCreate"))); // I18N to include original button name in help string
925            //status2.setText(Bundle.getMessage("RouteAddStatusInitial2", Bundle.getMessage("ButtonEdit")));
926            finishUpdate();
927            // get out of edit mode
928            editMode = false;
929            curRoute = null;
930        }
931        closeFrame();
932    }
933
934    /**
935     * Respond to the Update button - update to Route Table.
936     *
937     * @param newRoute true if a new route; false otherwise
938     */
939    protected void updatePressed(boolean newRoute) {
940        // Check if the User Name has been changed
941        String uName = _userName.getText();
942        Route g = checkNamesOK();
943        if (g == null) {
944            return;
945        }
946        // User Name is unique, change it
947        g.setUserName(uName);
948        // clear the current Turnout information for this Route
949        g.clearOutputTurnouts();
950        g.clearOutputSensors();
951        // clear the current Sensor information for this Route
952        g.clearRouteSensors();
953        // add those indicated in the panel
954        initializeIncludedList();
955        setTurnoutInformation(g);
956        setSensorInformation(g);
957        // set the current values of the file names
958        g.setOutputScriptName(scriptFile.getText());
959        g.setOutputSoundName(soundFile.getText());
960        // add Control Sensors and a Control Turnout if entered in the panel
961        setControlInformation(g);
962        curRoute = g;
963        finishUpdate();
964        status1.setForeground(Color.gray);
965        status1.setText((newRoute ? Bundle.getMessage("RouteAddStatusCreated") :
966                Bundle.getMessage("RouteAddStatusUpdated")) + ": \"" + uName + "\" (" + _includedTurnoutList.size() + " "
967                + Bundle.getMessage("Turnouts") + ", " + _includedSensorList.size() + " " + Bundle.getMessage("Sensors") + ")");
968
969        closeFrame();
970    }
971
972    /**
973     * Check name and return a new or existing Route object with the name as entered in the _systemName field on the
974     * addFrame pane.
975     *
976     * @return the new/updated Route object
977     */
978    private Route checkNamesOK() {
979        // Get system name and user name
980        String sName = _systemName.getText();
981        String uName = _userName.getText();
982        Route g;
983        if (_autoSystemName.isSelected() && !editMode) {
984            log.debug("checkNamesOK new autogroup");
985            // create new Route with auto system name
986            g = routeManager.newRoute(uName);
987        } else {
988            if (sName.length() == 0) {
989                status1.setText(Bundle.getMessage("AddBeanStatusEnter"));
990                status1.setForeground(Color.red);
991                return null;
992            }
993            try {
994                sName = routeManager.makeSystemName(sName);
995                g = routeManager.provideRoute(sName, uName);
996            } catch (IllegalArgumentException ex) {
997                g = null; // for later check:
998            }
999        }
1000        if (g == null) {
1001            // should never get here
1002            log.error("Unknown failure to create Route with System Name: {}", sName); // NOI18N
1003        } else {
1004            g.deActivateRoute();
1005        }
1006        return g;
1007    }
1008
1009    protected void closeFrame(){
1010        // remind to save, if Route was created or edited
1011        if (routeDirty) {
1012            showReminderMessage();
1013            routeDirty = false;
1014        }
1015        // hide addFrame
1016        setVisible(false);
1017
1018        // if in Edit, cancel edit mode
1019        if (editMode) {
1020            cancelEdit();
1021        }
1022        _routeSensorModel.dispose();
1023        _routeTurnoutModel.dispose();
1024        this.dispose();
1025    }
1026
1027    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractRouteAddEditFrame.class);
1028}