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        if (checkEnabled) return;
588        InstanceManager.getDefault(UserPreferencesManager.class).
589                showInfoMessage(Bundle.getMessage("ReminderTitle"),  // NOI18N
590                        Bundle.getMessage("ReminderSaveString", Bundle.getMessage("MenuItemRouteTable")),  // NOI18N
591                        getClassName(), "remindSaveRoute"); // NOI18N
592    }
593
594    private int sensorModeFromBox(JComboBox<String> box) {
595        String mode = (String) box.getSelectedItem();
596        return sensorModeFromString(mode);
597    }
598
599    int sensorModeFromString(String mode) {
600        int result = StringUtil.getStateFromName(mode, sensorInputModeValues, sensorInputModes);
601
602        if (result < 0) {
603            log.warn("unexpected mode string in sensorMode: {}", mode);
604            throw new IllegalArgumentException();
605        }
606        return result;
607    }
608
609    void setSensorModeBox(int mode, JComboBox<String> box) {
610        String result = StringUtil.getNameFromState(mode, sensorInputModeValues, sensorInputModes);
611        box.setSelectedItem(result);
612    }
613
614    private int turnoutModeFromBox(JComboBox<String> box) {
615        String mode = (String) box.getSelectedItem();
616        int result = StringUtil.getStateFromName(mode, turnoutInputModeValues, turnoutInputModes);
617
618        if (result < 0) {
619            log.warn("unexpected mode string in turnoutMode: {}", mode);
620            throw new IllegalArgumentException();
621        }
622        return result;
623    }
624
625    void setTurnoutModeBox(int mode, JComboBox<String> box) {
626        String result = StringUtil.getNameFromState(mode, turnoutInputModeValues, turnoutInputModes);
627        box.setSelectedItem(result);
628    }
629
630    /**
631     * Set the Turnout information for adding or editing.
632     *
633     * @param g the route to add the turnout to
634     */
635    protected void setTurnoutInformation(Route g) {
636        for (RouteTurnout t : _includedTurnoutList) {
637            g.addOutputTurnout(t.getDisplayName(), t.getState());
638        }
639    }
640
641    /**
642     * Sets the Sensor information for adding or editing.
643     *
644     * @param g the route to add the sensor to
645     */
646    protected void setSensorInformation(Route g) {
647        for (RouteSensor s : _includedSensorList) {
648            g.addOutputSensor(s.getDisplayName(), s.getState());
649        }
650    }
651
652    /**
653     * Set the Sensor, Turnout, and delay control information for adding or editing.
654     *
655     * @param g the route to configure
656     */
657    protected void setControlInformation(Route g) {
658        // Get sensor control information if any
659        Sensor sensor = sensor1.getSelectedItem();
660        if (sensor != null) {
661            if ((!g.addSensorToRoute(sensor.getSystemName(), sensorModeFromBox(sensor1mode)))) {
662                log.error("Unexpected failure to add Sensor '{}' to route '{}'.", sensor.getSystemName(), g.getSystemName());
663            }
664        }
665
666        if (sensor2.getSelectedItem() != null) {
667            if ((!g.addSensorToRoute(sensor2.getSelectedItemDisplayName(), sensorModeFromBox(sensor2mode)))) {
668                log.error("Unexpected failure to add Sensor '{}' to Route '{}'.", sensor2.getSelectedItemDisplayName(), g.getSystemName());
669            }
670        }
671
672        if (sensor3.getSelectedItem() != null) {
673            if ((!g.addSensorToRoute(sensor3.getSelectedItemDisplayName(), sensorModeFromBox(sensor3mode)))) {
674                log.error("Unexpected failure to add Sensor '{}' to Route '{}'.", sensor3.getSelectedItemDisplayName(), g.getSystemName());
675            }
676        }
677
678        //Turnouts Aligned sensor
679        if (turnoutsAlignedSensor.getSelectedItem() != null) {
680            g.setTurnoutsAlignedSensor(turnoutsAlignedSensor.getSelectedItemDisplayName());
681        } else {
682            g.setTurnoutsAlignedSensor("");
683        }
684
685        // Set turnout information if there is any
686        if (cTurnout.getSelectedItem() != null) {
687            g.setControlTurnout(cTurnout.getSelectedItemDisplayName());
688            // set up Control Turnout state
689            g.setControlTurnoutState(turnoutModeFromBox(cTurnoutStateBox));
690            g.setControlTurnoutFeedback(cTurnoutFeedbackBox.getSelectedIndex() == 1);
691            
692        } else {
693            // No Control Turnout was entered
694            g.setControlTurnout("");
695        }
696        // set route specific Delay information, see jmri.implementation.DefaultRoute#SetRouteThread()
697        int addDelay = (Integer) timeDelay.getValue(); // from a JSpinner with 0 set as minimum
698        g.setRouteCommandDelay(addDelay);
699
700        // Set Lock Turnout information if there is any
701        if (cLockTurnout.getSelectedItem() != null) {
702            g.setLockControlTurnout(cLockTurnout.getSelectedItemDisplayName());
703            // set up control turnout state
704            g.setLockControlTurnoutState(turnoutModeFromBox(cLockTurnoutStateBox));
705        } else {
706            // No Lock Turnout was entered
707            g.setLockControlTurnout("");
708        }
709    }
710
711    /**
712     * Set the sound file.
713     */
714    private void setSoundPressed() {
715        if (soundChooser == null) {
716            soundChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
717            soundChooser.setFileFilter(new jmri.util.NoArchiveFileFilter());
718        }
719        soundChooser.rescanCurrentDirectory();
720        int retVal = soundChooser.showOpenDialog(null);
721        // handle selection or cancel
722        if (retVal == JFileChooser.APPROVE_OPTION) {
723            try {
724                soundFile.setText(soundChooser.getSelectedFile().getCanonicalPath());
725            } catch (java.io.IOException e) {
726                log.error("exception setting sound file: ", e);
727            }
728        }
729    }
730
731    /**
732     * Set the script file.
733     */
734    private void setScriptPressed() {
735        if (scriptChooser == null) {
736            scriptChooser = new ScriptFileChooser();
737        }
738        scriptChooser.rescanCurrentDirectory();
739        int retVal = scriptChooser.showOpenDialog(null);
740        // handle selection or cancel
741        if (retVal == JFileChooser.APPROVE_OPTION) {
742            try {
743                scriptFile.setText(scriptChooser.getSelectedFile().getCanonicalPath());
744            } catch (java.io.IOException e) {
745                log.error("exception setting script file: ", e);
746            }
747        }
748    }
749
750
751    protected void finishUpdate() {
752        // move to show all Turnouts if not there
753        cancelIncludedOnly();
754        // Provide feedback to user
755        // switch GUI back to selection mode
756        //status2.setText(Bundle.getMessage("RouteAddStatusInitial2", Bundle.getMessage("ButtonEdit")));
757        status2.setVisible(true);
758        autoSystemName();
759        setTitle(Bundle.getMessage("TitleAddRoute"));
760        clearPage();
761        // reactivate the Route
762        routeDirty = true;
763        // get out of edit mode
764        editMode = false;
765        if (curRoute != null) {
766            curRoute.activateRoute();
767        }
768    }
769
770    /**
771     * Populate the page fields.  The route names are not included since they are handled
772     * by the Edit or Add actions.
773     * <p>
774     * The route is either the route being edited or a source route for doing a copy during
775     * the add action.
776     *
777     * @param route The route that contains the content.
778     */
779    protected void setPageContent(Route route) {
780        // set up Turnout list for this route
781        int setRow = 0;
782        for (int i = _turnoutList.size() - 1; i >= 0; i--) {
783            RouteTurnout turnout = _turnoutList.get(i);
784            String tSysName = turnout.getSysName();
785            if (route.isOutputTurnoutIncluded(tSysName)) {
786                turnout.setIncluded(true);
787                turnout.setState(route.getOutputTurnoutSetState(tSysName));
788                setRow = i;
789            } else {
790                turnout.setIncluded(false);
791                turnout.setState(Turnout.CLOSED);
792            }
793        }
794        setRow -= 1;
795        if (setRow < 0) {
796            setRow = 0;
797        }
798        _routeTurnoutScrollPane.getVerticalScrollBar().setValue(setRow * ROW_HEIGHT);
799        _routeTurnoutModel.fireTableDataChanged();
800
801        // set up Sensor list for this route
802        for (int i = _sensorList.size() - 1; i >= 0; i--) {
803            RouteSensor sensor = _sensorList.get(i);
804            String tSysName = sensor.getSysName();
805            if (route.isOutputSensorIncluded(tSysName)) {
806                sensor.setIncluded(true);
807                sensor.setState(route.getOutputSensorSetState(tSysName));
808                setRow = i;
809            } else {
810                sensor.setIncluded(false);
811                sensor.setState(Sensor.INACTIVE);
812            }
813        }
814        setRow -= 1;
815        if (setRow < 0) {
816            setRow = 0;
817        }
818        _routeSensorScrollPane.getVerticalScrollBar().setValue(setRow * ROW_HEIGHT);
819        _routeSensorModel.fireTableDataChanged();
820
821        // get Sound and  Script file names
822        scriptFile.setText(route.getOutputScriptName());
823        soundFile.setText(route.getOutputSoundName());
824
825        // get Turnout Aligned sensor
826        turnoutsAlignedSensor.setSelectedItem(route.getTurnoutsAlgdSensor());
827
828        // set up Control Sensors if there are any
829        Sensor[] temNames = new Sensor[Route.MAX_CONTROL_SENSORS];
830        int[] temModes = new int[Route.MAX_CONTROL_SENSORS];
831        for (int k = 0; k < Route.MAX_CONTROL_SENSORS; k++) {
832            temNames[k] = route.getRouteSensor(k);
833            temModes[k] = route.getRouteSensorMode(k);
834        }
835        sensor1.setSelectedItem(temNames[0]);
836        setSensorModeBox(temModes[0], sensor1mode);
837
838        sensor2.setSelectedItem(temNames[1]);
839        setSensorModeBox(temModes[1], sensor2mode);
840
841        sensor3.setSelectedItem(temNames[2]);
842        setSensorModeBox(temModes[2], sensor3mode);
843
844        // set up Control Turnout if there is one
845        cTurnout.setSelectedItem(route.getCtlTurnout());
846
847        setTurnoutModeBox(route.getControlTurnoutState(), cTurnoutStateBox);
848        
849        if (route.getControlTurnoutFeedback()) {
850            cTurnoutFeedbackBox.setSelectedIndex(1); // Known
851        } else {
852            cTurnoutFeedbackBox.setSelectedIndex(0);  // Commanded
853        }
854
855        // set up Lock Control Turnout if there is one
856        cLockTurnout.setSelectedItem(route.getLockCtlTurnout());
857
858        setTurnoutModeBox(route.getLockControlTurnoutState(), cLockTurnoutStateBox);
859
860        // set up additional route specific Delay
861        timeDelay.setValue(route.getRouteCommandDelay());
862    }
863
864    private void clearPage() {
865        _systemName.setText("");
866        _userName.setText("");
867        sensor1.setSelectedItem(null);
868        sensor2.setSelectedItem(null);
869        sensor3.setSelectedItem(null);
870        cTurnout.setSelectedItem(null);
871        cLockTurnout.setSelectedItem(null);
872        turnoutsAlignedSensor.setSelectedItem(null);
873        soundFile.setText("");
874        scriptFile.setText("");
875        for (int i = _turnoutList.size() - 1; i >= 0; i--) {
876            _turnoutList.get(i).setIncluded(false);
877        }
878        for (int i = _sensorList.size() - 1; i >= 0; i--) {
879            _sensorList.get(i).setIncluded(false);
880        }
881    }
882
883
884    /**
885     * Cancel included Turnouts only option
886     */
887    private void cancelIncludedOnly() {
888        if (!showAll) {
889            allButton.doClick();
890        }
891    }
892
893    private String getClassName() {
894        return this.getClass().getName();
895    }
896
897    List<RouteTurnout> get_turnoutList() {
898        return _turnoutList;
899    }
900
901    List<RouteTurnout> get_includedTurnoutList() {
902        return _includedTurnoutList;
903    }
904
905    List<RouteSensor> get_sensorList() {
906        return _sensorList;
907    }
908
909    List<RouteSensor> get_includedSensorList() {
910        return _includedSensorList;
911    }
912
913    public boolean isShowAll() {
914        return showAll;
915    }
916
917    /**
918     * Cancels edit mode
919     */
920    protected void cancelEdit() {
921        if (editMode) {
922            status1.setText(Bundle.getMessage("RouteAddStatusInitial1", Bundle.getMessage("ButtonCreate"))); // I18N to include original button name in help string
923            //status2.setText(Bundle.getMessage("RouteAddStatusInitial2", Bundle.getMessage("ButtonEdit")));
924            finishUpdate();
925            // get out of edit mode
926            editMode = false;
927            curRoute = null;
928        }
929        closeFrame();
930    }
931
932    /**
933     * Respond to the Update button - update to Route Table.
934     *
935     * @param newRoute true if a new route; false otherwise
936     */
937    protected void updatePressed(boolean newRoute) {
938        // Check if the User Name has been changed
939        String uName = _userName.getText();
940        Route g = checkNamesOK();
941        if (g == null) {
942            return;
943        }
944        // User Name is unique, change it
945        g.setUserName(uName);
946        // clear the current Turnout information for this Route
947        g.clearOutputTurnouts();
948        g.clearOutputSensors();
949        // clear the current Sensor information for this Route
950        g.clearRouteSensors();
951        // add those indicated in the panel
952        initializeIncludedList();
953        setTurnoutInformation(g);
954        setSensorInformation(g);
955        // set the current values of the file names
956        g.setOutputScriptName(scriptFile.getText());
957        g.setOutputSoundName(soundFile.getText());
958        // add Control Sensors and a Control Turnout if entered in the panel
959        setControlInformation(g);
960        curRoute = g;
961        finishUpdate();
962        status1.setForeground(Color.gray);
963        status1.setText((newRoute ? Bundle.getMessage("RouteAddStatusCreated") :
964                Bundle.getMessage("RouteAddStatusUpdated")) + ": \"" + uName + "\" (" + _includedTurnoutList.size() + " "
965                + Bundle.getMessage("Turnouts") + ", " + _includedSensorList.size() + " " + Bundle.getMessage("Sensors") + ")");
966    }
967
968    /**
969     * Check name and return a new or existing Route object with the name as entered in the _systemName field on the
970     * addFrame pane.
971     *
972     * @return the new/updated Route object
973     */
974    private Route checkNamesOK() {
975        // Get system name and user name
976        String sName = _systemName.getText();
977        String uName = _userName.getText();
978        Route g;
979        if (_autoSystemName.isSelected() && !editMode) {
980            log.debug("checkNamesOK new autogroup");
981            // create new Route with auto system name
982            g = routeManager.newRoute(uName);
983        } else {
984            if (sName.length() == 0) {
985                status1.setText(Bundle.getMessage("AddBeanStatusEnter"));
986                status1.setForeground(Color.red);
987                return null;
988            }
989            try {
990                sName = routeManager.makeSystemName(sName);
991                g = routeManager.provideRoute(sName, uName);
992            } catch (IllegalArgumentException ex) {
993                g = null; // for later check:
994            }
995        }
996        if (g == null) {
997            // should never get here
998            log.error("Unknown failure to create Route with System Name: {}", sName); // NOI18N
999        } else {
1000            g.deActivateRoute();
1001        }
1002        return g;
1003    }
1004
1005    protected void closeFrame(){
1006        // remind to save, if Route was created or edited
1007        if (routeDirty) {
1008            showReminderMessage();
1009            routeDirty = false;
1010        }
1011        // hide addFrame
1012        setVisible(false);
1013
1014        // if in Edit, cancel edit mode
1015        if (editMode) {
1016            cancelEdit();
1017        }
1018        _routeSensorModel.dispose();
1019        _routeTurnoutModel.dispose();
1020        this.dispose();
1021    }
1022
1023    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractRouteAddEditFrame.class);
1024}