001package jmri.jmrit.beantable;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.Container;
006import java.awt.FlowLayout;
007import java.awt.event.ActionEvent;
008import java.beans.PropertyChangeListener;
009import java.util.ArrayList;
010import javax.swing.*;
011import javax.swing.border.Border;
012import javax.swing.table.AbstractTableModel;
013import javax.swing.table.TableCellEditor;
014import javax.swing.table.TableColumn;
015import javax.swing.table.TableColumnModel;
016import javax.swing.table.TableRowSorter;
017import jmri.*;
018import jmri.swing.RowSorterUtil;
019import jmri.util.JmriJFrame;
020import jmri.util.StringUtil;
021import jmri.util.table.ButtonEditor;
022import jmri.util.table.ButtonRenderer;
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026/**
027 * Swing action to create and register a SignalGroup - Signal Head Edit Table.
028 * <p>
029 * Based in part on RouteTableAction.java and SignalGroupTableAction.java by Bob Jacobsen
030 *
031 * @author Kevin Dickerson Copyright (C) 2010
032 * @author Egbert Broerse 2017
033
034 */
035public class SignalGroupSubTableAction {
036    
037    /**
038     * Create an action with a specific title.
039     * <p>
040     * Note that the argument is the Action title, not the title of the
041     * resulting frame. Perhaps this should be changed?
042     *
043     * @param s title of the action
044     */
045    public SignalGroupSubTableAction(String s) {
046    }
047
048    public SignalGroupSubTableAction() {
049        this("Signal Group Head Edit Table");
050    } // NOI18N is never displayed on screen
051
052    String helpTarget() {
053        return "package.jmri.jmrit.beantable.SignalGroupTable";
054    }
055
056    /**
057     * Set choice for conditional evaluation.
058     * <p>
059     * Set to AND when you want all conditionals to be met for the Signal Head
060     * to turn On when an included Aspect is shown on the main Mast.
061     * Set to OR when you at least one of the conditionals to be met for the
062     * Signal Head to turn On when an included Aspect is shown.
063     *
064     * See {@link #operFromBox}
065     * 
066     * @param mode True for AND
067     * @param box the comboBox object to set
068     */
069    void setoperBox(boolean mode, JComboBox<String> box) {
070        int _mode = 0; // OR
071        if (mode) {
072            _mode = 1; // AND
073        }
074        String result = StringUtil.getNameFromState(_mode, operValues, oper);
075        box.setSelectedItem(result);
076    }
077
078    /**
079     * Get the user choice for conditional evaluation.
080     *
081     * See {@link #setoperBox}
082     * 
083     * @param box the comboBox object containing the user choice
084     * @return True for AND, False for OR
085     */
086    boolean operFromBox(JComboBox<String> box) {
087        String mode = (String) box.getSelectedItem();
088        int result = StringUtil.getStateFromName(mode, operValues, oper);
089
090        if (result < 0) {
091            log.warn("unexpected mode string in Signal Head Appearance Mode: {}", mode);
092            throw new IllegalArgumentException();
093        }
094        return result != 0;
095    }
096
097    private static String[] oper = new String[]{"AND", "OR"};
098    private static int[] operValues = new int[]{0x00, 0x01};
099
100    /**
101     * Get the user choice for a Signal Group Signal Head's On and Off Appearance
102     * from a comboBox at the top of the Edit Head sub pane.
103     *
104     * @param box the comboBox object containing the user choice
105     * @return Value for the Appearance (color) set i.e. 0 for DARK
106     */
107    int headStateFromBox(JComboBox<String> box) throws IllegalArgumentException {
108        SignalHead sig = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(curHeadName);
109        int result;
110        String mode = (String) box.getSelectedItem();
111        if (sig != null) {
112            result = StringUtil.getStateFromName(mode, sig.getValidStates(), sig.getValidStateNames());
113        } else {
114            result = StringUtil.getStateFromName(mode, signalStatesValues, signalStates);
115        }
116
117        if (result < 0) {
118            log.warn("unexpected mode string in signalHeadMode: {}", mode);
119            throw new IllegalArgumentException();
120        }
121        return result;
122    }
123
124    /**
125     * Set selected item in a Signal Group Signal Head's On and Off Appearance
126     * in a comboBox at the top of the Edit Head sub pane.
127     *
128     * @param mode Value for an Appearance (color) i.e. 0 for DARK
129     * @param box the comboBox object to set
130     */
131    void setSignalHeadStateBox(int mode, JComboBox<String> box) {
132        SignalHead sig = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(curHeadName);
133        if (sig != null) {
134            String result = StringUtil.getNameFromState(mode, sig.getValidStates(), sig.getValidStateNames());
135            box.setSelectedItem(result);
136        } else {
137            log.error("Failed to get signal head {}", curHeadName);
138        }
139    }
140
141    /**
142     * Get the user choice for a Sensor conditional's On state from the comboBox on the Edit Head sub pane.
143     * See {@link #turnoutModeFromBox}
144     * 
145     * @param box the comboBox object containing the user choice
146     * @return Value for ACTIVE/INACTIVE
147     */
148    int sensorModeFromBox(JComboBox<String> box) {
149        String mode = (String) box.getSelectedItem();
150        int result = StringUtil.getStateFromName(mode, sensorInputModeValues, sensorInputModes);
151
152        if (result < 0) {
153            log.warn("unexpected mode string in Signal Head Appearance: {}", mode);
154            throw new IllegalArgumentException();
155        }
156        return result;
157    }
158
159    /**
160     * Set selected item for a Sensor conditional's On state in the
161     * comboBox on the Edit Head sub pane.
162     *
163     * See {@link #turnoutModeFromBox}
164     * 
165     * @param mode Value for ACTIVE/INACTIVE
166     * @param box the comboBox object to set
167     */
168    void setSensorModeBox(int mode, JComboBox<String> box) {
169        String result = StringUtil.getNameFromState(mode, sensorInputModeValues, sensorInputModes);
170        box.setSelectedItem(result);
171    }
172
173    /**
174     * Get the user choice for a Control Turnout conditional's On state
175     * from the comboBox on the Edit Head sub pane.
176     *
177     * See {@link #sensorModeFromBox}
178     * 
179     * @param box the comboBox object containing the user choice
180     * @return Value for CLOSED/THROWN
181     */
182    int turnoutModeFromBox(JComboBox<String> box) {
183        String mode = (String) box.getSelectedItem();
184        int result = StringUtil.getStateFromName(mode, turnoutInputModeValues, turnoutInputModes);
185
186        if (result < 0) {
187            log.warn("unexpected mode string in turnoutMode: {}", mode);
188            throw new IllegalArgumentException();
189        }
190        return result;
191    }
192
193    /**
194     * Set selected item for a Control Turnout conditional's On state
195     * in the comboBox on the Edit Head sub pane.
196     *
197     * See {@link #turnoutModeFromBox}
198     * 
199     * @param mode Value for CLOSED/THROWN
200     * @param box the comboBox object to set
201     */
202    void setTurnoutModeBox(int mode, JComboBox<String> box) {
203        String result = StringUtil.getNameFromState(mode, turnoutInputModeValues, turnoutInputModes);
204        box.setSelectedItem(result);
205    }
206
207    JLabel _systemName;
208    JComboBox<String> _OnAppearance;
209    JComboBox<String> _OffAppearance;
210    JLabel spacer = new JLabel("       "); // to create space between On and Off Appearance choices
211    JComboBox<String> _SensorTurnoutOper = new JComboBox<>(oper);
212
213    JmriJFrame addSubFrame = null;
214    SignalGroupTurnoutModel _SignalGroupTurnoutModel;
215    JScrollPane _SignalGroupTurnoutScrollPane;
216    SignalGroupSensorModel _SignalGroupSensorModel;
217    JScrollPane _SignalGroupSensorScrollPane;
218
219    ButtonGroup selGroup = null;
220    JRadioButton allButton = null;
221    JRadioButton includedButton = null;
222
223    JLabel nameLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameSignalHead")));
224    JLabel signalOnStateLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("OnAppearance")));
225    JLabel signalOffStateLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("OffAppearance")));
226    JLabel userLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("SelectConditionsOn")));
227
228    JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel"));
229    JButton updateSubButton = new JButton(Bundle.getMessage("ButtonApply"));
230
231    static String updateInst = Bundle.getMessage("ClickToApply", Bundle.getMessage("ButtonApply"));
232
233    JLabel status1 = new JLabel(updateInst);
234
235    JPanel p2xt = null;   // Turnout list table
236    JPanel p2xs = null;   // Sensor list table
237
238    SignalGroup curSignalGroup = null;
239    String curHeadName;
240    SignalHead curSignalHead;
241
242    /**
243     * Open an editor to set the details of a Signal Head as part of a Signal Group.
244     * Called when user clicks the Edit button for a Head in the Add/Edit Signal Group pane.
245     *
246     * @see SignalGroupTableAction#signalHeadEditPressed(int) SignalGroupTableAction.signalHeadEditPressed
247     * @param g Parent Signal Head
248     * @param headName System or User Name of this Signal Head
249     */
250    void editHead(SignalGroup g, String headName) {
251        curSignalGroup = g;
252        curHeadName = headName;
253        curSignalHead = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(curHeadName);
254        if (curSignalHead != null) {
255            _OnAppearance = new JComboBox<>(curSignalHead.getValidStateNames()); // shows i18n strings from signal head definition
256            _OffAppearance = new JComboBox<>(curSignalHead.getValidStateNames());
257        }
258        _systemName = new JLabel(headName);
259        _systemName.setVisible(true);
260
261        TurnoutManager tm = InstanceManager.getDefault(TurnoutManager.class);
262         _turnoutList = new ArrayList<>(tm.getNamedBeanSet().size());
263         tm.getNamedBeanSet().stream().filter(turn -> (turn != null)).forEachOrdered(turn -> {
264             _turnoutList.add(new SignalGroupTurnout(turn.getSystemName(), turn.getUserName()));
265        });
266
267        SensorManager sm = InstanceManager.getDefault(SensorManager.class);
268        
269        _sensorList = new ArrayList<>(sm.getNamedBeanSet().size());
270        sm.getNamedBeanSet().stream().filter(sen -> (sen != null)).forEachOrdered(sen -> {
271            _sensorList.add(new SignalGroupSensor(sen.getSystemName(), sen.getUserName()));
272        });
273        initializeIncludedList();
274
275        // Set up sub panel for editing of a Signal Group Signal Head item
276        if (addSubFrame == null) { // create one if not yet available
277            addSubFrame = new JmriJFrame((Bundle.getMessage("EditSignalGroup") + " - " + Bundle.getMessage("BeanNameSignalHead")), false, true);
278            addSubFrame.addHelpMenu("package.jmri.jmrit.beantable.SignalGroupAddEdit", true);
279            addSubFrame.setLocation(100, 30);
280            addSubFrame.getContentPane().setLayout(new BoxLayout(addSubFrame.getContentPane(), BoxLayout.Y_AXIS));
281            Container contentPane = addSubFrame.getContentPane();
282            // add system name label
283            JPanel ps = new JPanel();
284            ps.setLayout(new FlowLayout());
285            ps.add(nameLabel);
286            ps.add(_systemName);
287            contentPane.add(ps);
288            // add user name label
289            JPanel pc = new JPanel();
290            pc.setLayout(new FlowLayout());
291            pc.add(signalOnStateLabel);
292            pc.add(_OnAppearance); // comboBox to set On Appearance
293            _OnAppearance.setToolTipText(Bundle.getMessage("StateWhenMetTooltip"));
294            pc.add(spacer);
295            pc.add(signalOffStateLabel);
296            pc.add(_OffAppearance); // comboBox to set On Appearance
297            _OffAppearance.setToolTipText(Bundle.getMessage("StateWhenNotMetTooltip"));
298            contentPane.add(pc);
299
300            JPanel p = new JPanel();
301            p.setLayout(new FlowLayout());
302            p.add(userLabel);
303            contentPane.add(p);
304            // fill in info for the Signal Head being configured
305            if (curSignalHead.getClass().getName().contains("SingleTurnoutSignalHead")) {
306                jmri.implementation.SingleTurnoutSignalHead stsh = (jmri.implementation.SingleTurnoutSignalHead) InstanceManager.getDefault(SignalHeadManager.class).getByUserName(curHeadName);
307                // we may use a user name in the editing pane, so look for that first
308                if (stsh == null) {
309                    stsh = (jmri.implementation.SingleTurnoutSignalHead) InstanceManager.getDefault(SignalHeadManager.class).getBySystemName(curHeadName);
310                    // when user name is empty, get by user name
311                }
312                if (stsh != null) {
313                    log.debug("SGsubTA #279 editHead: setting props for signal head {}", curHeadName);
314                    if ((g.getHeadOnState(curSignalHead) == 0x00) && (g.getHeadOffState(curSignalHead) == 0x00)) {
315                        g.setHeadOnState(curSignalHead, stsh.getOnAppearance());
316                        g.setHeadOffState(curSignalHead, stsh.getOffAppearance());
317                    }
318                } else {
319                    // nothing found
320                    log.error("Failed to get signal head object named {}", curHeadName);
321                }
322            }
323            setSignalHeadStateBox(g.getHeadOnState(curSignalHead), _OnAppearance);
324            setSignalHeadStateBox(g.getHeadOffState(curSignalHead), _OffAppearance);
325
326            // add Turnout Display Choice
327            JPanel py = new JPanel();
328            py.add(new JLabel(Bundle.getMessage("Show")));
329            selGroup = new ButtonGroup();
330            allButton = new JRadioButton(Bundle.getMessage("All"), true);
331            selGroup.add(allButton);
332            py.add(allButton);
333            allButton.addActionListener((ActionEvent e) -> {
334                // Setup for display of all Turnouts, if needed
335                if (!showAll) {
336                    showAll = true;
337                    _SignalGroupTurnoutModel.fireTableDataChanged();
338                    _SignalGroupSensorModel.fireTableDataChanged();
339                }
340            });
341            includedButton = new JRadioButton(Bundle.getMessage("Included"), false);
342            selGroup.add(includedButton);
343            py.add(includedButton);
344            includedButton.addActionListener((ActionEvent e) -> {
345                // Setup for display of included Turnouts only, if needed
346                if (showAll) {
347                    showAll = false;
348                    initializeIncludedList();
349                    _SignalGroupTurnoutModel.fireTableDataChanged();
350                    _SignalGroupSensorModel.fireTableDataChanged();
351                }
352            });
353            py.add(new JLabel("  " + Bundle.getMessage("_and_", Bundle.getMessage("Turnouts"), Bundle.getMessage("Sensors"))));
354            contentPane.add(py);
355
356            // add turnout table
357            p2xt = new JPanel();
358            JPanel p2xtSpace = new JPanel();
359            p2xtSpace.setLayout(new BoxLayout(p2xtSpace, BoxLayout.Y_AXIS));
360            p2xtSpace.add(new JLabel("XXX"));
361            p2xt.add(p2xtSpace);
362
363            JPanel p21t = new JPanel();
364            p21t.setLayout(new BoxLayout(p21t, BoxLayout.Y_AXIS));
365            p21t.add(new JLabel(Bundle.getMessage("SelectInGroup", Bundle.getMessage("Turnouts"))));
366            p2xt.add(p21t);
367            _SignalGroupTurnoutModel = new SignalGroupTurnoutModel();
368            JTable SignalGroupTurnoutTable = new JTable(_SignalGroupTurnoutModel);
369            TableRowSorter<SignalGroupTurnoutModel> sgtSorter = new TableRowSorter<>(_SignalGroupTurnoutModel);
370
371            // use NamedBean's built-in Comparator interface for sorting the system name column
372            RowSorterUtil.setSortOrder(sgtSorter, SignalGroupTurnoutModel.SNAME_COLUMN, SortOrder.ASCENDING);
373            SignalGroupTurnoutTable.setRowSorter(sgtSorter);
374            SignalGroupTurnoutTable.setRowSelectionAllowed(false);
375            SignalGroupTurnoutTable.setPreferredScrollableViewportSize(new java.awt.Dimension(480, 80));
376
377            SignalGroupSubTableAction.setRowHeight(SignalGroupTurnoutTable.getRowHeight());
378            JComboBox<String> stateTCombo = new JComboBox<>();
379            stateTCombo.addItem(SET_TO_CLOSED);
380            stateTCombo.addItem(SET_TO_THROWN);
381            TableColumnModel SignalGroupTurnoutColumnModel = SignalGroupTurnoutTable.getColumnModel();
382            TableColumn includeColumnT = SignalGroupTurnoutColumnModel.
383                    getColumn(SignalGroupTurnoutModel.INCLUDE_COLUMN);
384            includeColumnT.setResizable(false);
385            includeColumnT.setMinWidth(50);
386            includeColumnT.setMaxWidth(60);
387            TableColumn sNameColumnT = SignalGroupTurnoutColumnModel.
388                    getColumn(SignalGroupTurnoutModel.SNAME_COLUMN);
389            sNameColumnT.setResizable(true);
390            sNameColumnT.setMinWidth(75);
391            sNameColumnT.setMaxWidth(95);
392            TableColumn uNameColumnT = SignalGroupTurnoutColumnModel.
393                    getColumn(SignalGroupTurnoutModel.UNAME_COLUMN);
394            uNameColumnT.setResizable(true);
395            uNameColumnT.setMinWidth(210);
396            uNameColumnT.setMaxWidth(260);
397            TableColumn stateColumnT = SignalGroupTurnoutColumnModel.
398                    getColumn(SignalGroupTurnoutModel.STATE_COLUMN);
399            stateColumnT.setCellEditor(new DefaultCellEditor(stateTCombo));
400            stateColumnT.setResizable(false);
401            stateColumnT.setMinWidth(90);
402            stateColumnT.setMaxWidth(100);
403            _SignalGroupTurnoutScrollPane = new JScrollPane(SignalGroupTurnoutTable);
404            p2xt.add(_SignalGroupTurnoutScrollPane, BorderLayout.CENTER);
405            contentPane.add(p2xt);
406            p2xt.setVisible(true);
407
408            JPanel po = new JPanel();
409            po.setLayout(new FlowLayout());
410            JLabel operLabel = new JLabel(Bundle.getMessage("ChooseOrAnd"));
411            po.add(operLabel);
412            po.add(_SensorTurnoutOper);
413            contentPane.add(po);
414
415            // add sensor table
416            p2xs = new JPanel();
417            JPanel p2xsSpace = new JPanel();
418            p2xsSpace.setLayout(new BoxLayout(p2xsSpace, BoxLayout.Y_AXIS));
419            p2xsSpace.add(new JLabel("XXX"));
420            p2xs.add(p2xsSpace);
421
422            JPanel p21s = new JPanel();
423            p21s.setLayout(new BoxLayout(p21s, BoxLayout.Y_AXIS));
424            p21s.add(new JLabel(Bundle.getMessage("SelectInGroup", Bundle.getMessage("Sensors"))));
425            p2xs.add(p21s);
426            _SignalGroupSensorModel = new SignalGroupSensorModel();
427            JTable SignalGroupSensorTable = new JTable(_SignalGroupSensorModel);
428            TableRowSorter<SignalGroupSensorModel> sgsSorter = new TableRowSorter<>(_SignalGroupSensorModel);
429
430            // use NamedBean's built-in Comparator interface for sorting the system name column
431            RowSorterUtil.setSortOrder(sgsSorter, SignalGroupSensorModel.SNAME_COLUMN, SortOrder.ASCENDING);
432            SignalGroupSensorTable.setRowSorter(sgsSorter);
433            SignalGroupSensorTable.setRowSelectionAllowed(false);
434            SignalGroupSensorTable.setPreferredScrollableViewportSize(new java.awt.Dimension(480, 80));
435            JComboBox<String> stateSCombo = new JComboBox<>();
436            stateSCombo.addItem(SET_TO_ACTIVE);
437            stateSCombo.addItem(SET_TO_INACTIVE);
438            TableColumnModel SignalGroupSensorColumnModel = SignalGroupSensorTable.getColumnModel();
439            TableColumn includeColumnS = SignalGroupSensorColumnModel.
440                    getColumn(SignalGroupSensorModel.INCLUDE_COLUMN);
441            includeColumnS.setResizable(false);
442            includeColumnS.setMinWidth(50);
443            includeColumnS.setMaxWidth(60);
444            TableColumn sNameColumnS = SignalGroupSensorColumnModel.
445                    getColumn(SignalGroupSensorModel.SNAME_COLUMN);
446            sNameColumnS.setResizable(true);
447            sNameColumnS.setMinWidth(75);
448            sNameColumnS.setMaxWidth(95);
449            TableColumn uNameColumnS = SignalGroupSensorColumnModel.
450                    getColumn(SignalGroupSensorModel.UNAME_COLUMN);
451            uNameColumnS.setResizable(true);
452            uNameColumnS.setMinWidth(210);
453            uNameColumnS.setMaxWidth(260);
454            TableColumn stateColumnS = SignalGroupSensorColumnModel.
455                    getColumn(SignalGroupSensorModel.STATE_COLUMN);
456            stateColumnS.setCellEditor(new DefaultCellEditor(stateSCombo));
457            stateColumnS.setResizable(false);
458            stateColumnS.setMinWidth(90);
459            stateColumnS.setMaxWidth(100);
460            _SignalGroupSensorScrollPane = new JScrollPane(SignalGroupSensorTable);
461            p2xs.add(_SignalGroupSensorScrollPane, BorderLayout.CENTER);
462            contentPane.add(p2xs);
463            p2xs.setVisible(true);
464
465            // add notes panel
466            JPanel pa = new JPanel();
467            pa.setLayout(new BoxLayout(pa, BoxLayout.Y_AXIS));
468            JPanel p1 = new JPanel();
469            p1.setLayout(new FlowLayout());
470            status1.setFont(status1.getFont().deriveFont(0.9f * nameLabel.getFont().getSize())); // a bit smaller
471            status1.setForeground(Color.gray);
472            p1.add(status1);
473            pa.add(p1);
474            Border pBorder = BorderFactory.createEtchedBorder();
475            pa.setBorder(pBorder);
476            contentPane.add(pa);
477
478            // add buttons - Add SignalGroup button
479            JPanel pb = new JPanel();
480            pb.setLayout(new FlowLayout(FlowLayout.TRAILING));
481            // add Cancel button
482            pb.add(cancelButton);
483            cancelButton.addActionListener(this::cancelSubPressed);
484            // add Update SignalGroup button
485            pb.add(updateSubButton);
486            updateSubButton.addActionListener((ActionEvent e) -> {
487                updateSubPressed(e, false);
488            });
489            updateSubButton.setToolTipText(Bundle.getMessage("TooltipUpdateGroup"));
490
491            p2xtSpace.setVisible(false);
492            p2xsSpace.setVisible(false);
493            updateSubButton.setVisible(true);
494            contentPane.add(pb);
495            addSubFrame.pack();
496        }
497        // set listener for window closing
498        addSubFrame.addWindowListener(new java.awt.event.WindowAdapter() {
499            @Override
500            public void windowClosing(java.awt.event.WindowEvent e) {
501                addSubFrame.setVisible(false);
502                cancelSubEdit();
503                _SignalGroupSensorModel.dispose();
504                _SignalGroupTurnoutModel.dispose();
505            }
506        });
507        addSubFrame.setVisible(true);
508        // add AND/OR choice box
509        setoperBox(curSignalGroup.getSensorTurnoutOper(curSignalHead), _SensorTurnoutOper);
510        setSignalHeadStateBox(curSignalGroup.getHeadOnState(curSignalHead), _OnAppearance);
511        setSignalHeadStateBox(curSignalGroup.getHeadOffState(curSignalHead), _OffAppearance);
512        int setRow = 0;
513        for (int i = _turnoutList.size() - 1; i >= 0; i--) {
514            SignalGroupTurnout turnout = _turnoutList.get(i);
515            Turnout tTurnout = turnout.getTurnout();
516            if (curSignalGroup.isTurnoutIncluded(curSignalHead, tTurnout)) {
517                turnout.setIncluded(true);
518                turnout.setState(curSignalGroup.getTurnoutState(curSignalHead, tTurnout));
519                setRow = i;
520            } else {
521                turnout.setIncluded(false);
522                turnout.setState(Turnout.CLOSED);
523            }
524        }
525        setRow -= 1;
526        if (setRow < 0) {
527            setRow = 0;
528        }
529        _SignalGroupTurnoutScrollPane.getVerticalScrollBar().setValue(setRow * ROW_HEIGHT);
530        _SignalGroupTurnoutModel.fireTableDataChanged();
531
532        for (int i = _sensorList.size() - 1; i >= 0; i--) {
533            SignalGroupSensor sensor = _sensorList.get(i);
534            Sensor tSensor = sensor.getSensor();
535            if (curSignalGroup.isSensorIncluded(curSignalHead, tSensor)) {
536                sensor.setIncluded(true);
537                sensor.setState(curSignalGroup.getSensorState(curSignalHead, tSensor));
538            } else {
539                sensor.setIncluded(false);
540                sensor.setState(Sensor.INACTIVE);
541            }
542        }
543
544        status1.setText(updateInst);
545        updateSubButton.setVisible(true);
546    }
547
548    /**
549     * Initialize the list of included turnouts and sensors for a
550     * Signal Head item on the sub pane.
551     */
552    void initializeIncludedList() {
553        _includedTurnoutList = new ArrayList<>();
554        for (int i = 0; i < _turnoutList.size(); i++) {
555            if (_turnoutList.get(i).isIncluded()) {
556                _includedTurnoutList.add(_turnoutList.get(i));
557            }
558        }
559        _includedSensorList = new ArrayList<>();
560        for (int i = 0; i < _sensorList.size(); i++) {
561            if (_sensorList.get(i).isIncluded()) {
562                _includedSensorList.add(_sensorList.get(i));
563            }
564        }
565    }
566
567    /**
568     * Set the Turnout information for adding or editing.
569     *
570     * @param g The Signal Group being configured
571     * @return total number of turnouts included in group
572     */
573    int setTurnoutInformation(SignalGroup g) {
574        for (int i = 0; i < _includedTurnoutList.size(); i++) {
575            SignalGroupTurnout t = _includedTurnoutList.get(i);
576            g.setHeadAlignTurnout(curSignalHead, t.getTurnout(), t.getState());
577        }
578        return _includedTurnoutList.size();
579    }
580
581    /**
582     * Set the Sensor information for adding or editing.
583     *
584     * @param g The Signal Group being configured
585     * @return total number of sensors included in group
586     */
587    int setSensorInformation(SignalGroup g) {
588        for (int i = 0; i < _includedSensorList.size(); i++) {
589            SignalGroupSensor s = _includedSensorList.get(i);
590            g.setHeadAlignSensor(curSignalHead, s.getSensor(), s.getState());
591        }
592        return _includedSensorList.size();
593    }
594
595    /**
596     * Respond to the Cancel button - clean up.
597     *
598     * @param e the event heard
599     */
600    void cancelSubPressed(ActionEvent e) {
601        log.debug("Edit Signal Group Head canceled in SGSTA line 569");
602        cancelSubEdit();
603        _SignalGroupSensorModel.dispose();
604        _SignalGroupTurnoutModel.dispose();
605    }
606
607    /**
608     * Respond to the Update button on the Edit Head sub pane - update to SignalGroup.
609     *
610     * @param e the event heard
611     * @param newSignalGroup True if this is a newly created Signal
612     *                       Group for which additional actions are required
613     */
614    void updateSubPressed(ActionEvent e, boolean newSignalGroup) {
615        curSignalGroup.clearHeadTurnout(curSignalHead);
616        curSignalGroup.clearHeadSensor(curSignalHead);
617        // store the new configuration as entered by the user
618        initializeIncludedList();
619        setTurnoutInformation(curSignalGroup);
620        setSensorInformation(curSignalGroup);
621        curSignalGroup.setHeadOnState(curSignalHead, headStateFromBox(_OnAppearance));
622        curSignalGroup.setHeadOffState(curSignalHead, headStateFromBox(_OffAppearance));
623        // store AND/OR operand user choice
624        curSignalGroup.setSensorTurnoutOper(curSignalHead, operFromBox(_SensorTurnoutOper));
625        // add control Sensors and a control Turnouts if entered in the window
626        finishUpdate();
627    }
628
629    /**
630     * Clean up the interface and reset Included radio button to All for next use
631     */
632    void finishUpdate() {
633        // show all turnouts and sensors if not yet set to All
634        cancelIncludedOnly();
635        updateSubButton.setVisible(false);
636        addSubFrame.setVisible(false);
637    }
638
639    /**
640     * Cancel edit mode
641     */
642    void cancelSubEdit() {
643        // get out of edit mode
644        curSignalGroup = null;
645        finishUpdate();
646    }
647
648    /**
649     * Cancel included Turnouts and Sensors only option
650     */
651    void cancelIncludedOnly() {
652        if (!showAll) {
653            allButton.doClick();
654        }
655    }
656
657    /**
658     * Base table model for selecting Signal Group Head control Conditionals
659     */
660    public abstract class SignalGroupOutputModel extends AbstractTableModel implements PropertyChangeListener {
661
662        @Override
663        public Class<?> getColumnClass(int c) {
664            if (c == INCLUDE_COLUMN) {
665                return Boolean.class;
666            } else {
667                return String.class;
668            }
669        }
670
671        @Override
672        public void propertyChange(java.beans.PropertyChangeEvent e) {
673            if (e.getPropertyName().equals("length")) {
674                // a new NamedBean is available in the manager
675                fireTableDataChanged();
676            }
677        }
678
679        @Override
680        public String getColumnName(int c) {
681            return COLUMN_NAMES[c];
682        }
683
684        @Override
685        public int getColumnCount() {
686            return 4;
687        }
688
689        @Override
690        public boolean isCellEditable(int r, int c) {
691            return ((c == INCLUDE_COLUMN) || (c == STATE_COLUMN));
692        }
693
694        public static final int SNAME_COLUMN = 0;
695        public static final int UNAME_COLUMN = 1;
696        public static final int INCLUDE_COLUMN = 2;
697        public static final int STATE_COLUMN = 3;
698
699        public String getDisplayName(int r) {
700            if (((String) getValueAt(r, UNAME_COLUMN) != null) || (!((String) getValueAt(r, UNAME_COLUMN)).isEmpty())) {
701                return (String) getValueAt(r, UNAME_COLUMN);
702            } else {
703                return (String) getValueAt(r, SNAME_COLUMN);
704            }
705        }
706
707    }
708
709    /**
710     * Table model for selecting Turnouts and their On State
711     * as Conditionals for a Signal Group Signal Head member.
712     */
713    class SignalGroupTurnoutModel extends SignalGroupOutputModel {
714
715        SignalGroupTurnoutModel() {
716            init();
717        }
718        
719        final void init(){
720            InstanceManager.getDefault(TurnoutManager.class).addPropertyChangeListener(this);
721        }
722        
723        public void dispose() {
724            InstanceManager.getDefault(TurnoutManager.class).removePropertyChangeListener(this);
725        }
726
727        @Override
728        public int getRowCount() {
729            return ( showAll ? _turnoutList.size() : _includedTurnoutList.size());
730        }
731
732        @Override
733        public Object getValueAt(int r, int c) {
734            ArrayList<SignalGroupTurnout> turnoutList = ( showAll ? _turnoutList : _includedTurnoutList );
735            // some error checking
736            if (r >= turnoutList.size()) {
737                log.debug("SGSTA getValueAt #703: row index is greater than turnout list size");
738                return null;
739            }
740            switch (c) {
741                case INCLUDE_COLUMN:
742                    return turnoutList.get(r).isIncluded();
743                case SNAME_COLUMN:  // slot number
744                    return turnoutList.get(r).getSysName();
745                case UNAME_COLUMN:  //
746                    return turnoutList.get(r).getUserName();
747                case STATE_COLUMN:  //
748                    return turnoutList.get(r).getSetToState();
749                default:
750                    return null;
751            }
752        }
753
754        @Override
755        public void setValueAt(Object type, int r, int c) {
756            ArrayList<SignalGroupTurnout> turnoutList = ( showAll ? _turnoutList : _includedTurnoutList );
757            switch (c) {
758                case INCLUDE_COLUMN:
759                    turnoutList.get(r).setIncluded((Boolean) type);
760                    break;
761                case STATE_COLUMN:
762                    turnoutList.get(r).setSetToState((String) type);
763                    break;
764                default:
765                    break;
766            }
767        }
768    }
769
770    /**
771     * Set up a table for selecting Sensors and Sensor On State
772     * as Conditionals for a Signal Group Signal Head member.
773     */
774    class SignalGroupSensorModel extends SignalGroupOutputModel {
775
776        SignalGroupSensorModel() {
777            init();
778        }
779        
780        final void init(){
781            InstanceManager.getDefault(SensorManager.class).addPropertyChangeListener(this);
782        }
783        
784        public void dispose(){
785            InstanceManager.getDefault(SensorManager.class).removePropertyChangeListener(this);
786        }
787
788        @Override
789        public int getRowCount() {
790            return (showAll ? _sensorList.size() : _includedSensorList.size() );
791        }
792
793        @Override
794        public Object getValueAt(int r, int c) {
795            ArrayList<SignalGroupSensor> sensorList = ( showAll ? _sensorList : _includedSensorList);
796            // some error checking
797            if (r >= sensorList.size()) {
798                log.debug("SGSTA getValueAt #766: row is greater than sensor list size");
799                return null;
800            }
801            switch (c) {
802                case INCLUDE_COLUMN:
803                    return sensorList.get(r).isIncluded();
804                case SNAME_COLUMN:  // slot number
805                    return sensorList.get(r).getSysName();
806                case UNAME_COLUMN:  //
807                    return sensorList.get(r).getUserName();
808                case STATE_COLUMN:  //
809                    return sensorList.get(r).getSetToState();
810                default:
811                    return null;
812            }
813        }
814
815        @Override
816        public void setValueAt(Object type, int r, int c) {
817            ArrayList<SignalGroupSensor> sensorList = ( showAll ? _sensorList : _includedSensorList );
818            switch (c) {
819                case INCLUDE_COLUMN:
820                    sensorList.get(r).setIncluded(((Boolean) type));
821                    break;
822                case STATE_COLUMN:
823                    sensorList.get(r).setSetToState((String) type);
824                    break;
825                default:
826                    break;
827            }
828        }
829    }
830
831    private boolean showAll = true; // false indicates: show only included Turnouts and Sensors
832
833    private static int ROW_HEIGHT;
834
835    private static String[] COLUMN_NAMES = {Bundle.getMessage("ColumnSystemName"),
836            Bundle.getMessage("ColumnUserName"),
837            Bundle.getMessage("Include"),
838            Bundle.getMessage("ColumnLabelSetState")};
839    private static String SET_TO_ACTIVE = Bundle.getMessage("SensorStateActive");
840    private static String SET_TO_INACTIVE = Bundle.getMessage("SensorStateInactive");
841    private final static String SET_TO_CLOSED = InstanceManager.getDefault(TurnoutManager.class).getClosedText();
842    private final static String SET_TO_THROWN = InstanceManager.getDefault(TurnoutManager.class).getThrownText();
843
844    private static String[] sensorInputModes = new String[]{Bundle.getMessage("SensorStateActive"), Bundle.getMessage("SensorStateInactive")};
845    private static int[] sensorInputModeValues = new int[]{SignalGroup.ONACTIVE, SignalGroup.ONINACTIVE};
846
847    private static String[] signalStates = new String[]{Bundle.getMessage("SignalHeadStateDark"), Bundle.getMessage("SignalHeadStateRed"), Bundle.getMessage("SignalHeadStateYellow"), Bundle.getMessage("SignalHeadStateGreen"), Bundle.getMessage("SignalHeadStateLunar")};
848    private static int[] signalStatesValues = new int[]{SignalHead.DARK, SignalHead.RED, SignalHead.YELLOW, SignalHead.GREEN, SignalHead.LUNAR};
849
850    private static String[] turnoutInputModes = new String[]{SET_TO_CLOSED,SET_TO_THROWN};
851    private static int[] turnoutInputModeValues = new int[]{SignalGroup.ONCLOSED, SignalGroup.ONTHROWN};
852
853    private ArrayList<SignalGroupTurnout> _turnoutList;      // array of all Turnouts
854    private ArrayList<SignalGroupTurnout> _includedTurnoutList;
855
856    private ArrayList<SignalGroupSensor> _sensorList;        // array of all Sensors
857    private ArrayList<SignalGroupSensor> _includedSensorList;
858
859    private synchronized static void setRowHeight(int newVal) {
860        ROW_HEIGHT = newVal;
861    }
862
863    private abstract class SignalGroupElement {
864
865        String _sysName;
866        String _userName;
867        boolean _included;
868        int _setToState;
869
870        SignalGroupElement(String sysName, String userName) {
871            _sysName = sysName;
872            _userName = userName;
873            _included = false;
874            _setToState = Sensor.INACTIVE;
875        }
876
877        String getSysName() {
878            return _sysName;
879        }
880
881        String getUserName() {
882            return _userName;
883        }
884
885        boolean isIncluded() {
886            return _included;
887        }
888
889        void setIncluded(boolean include) {
890            _included = include;
891        }
892
893        abstract String getSetToState();
894
895        abstract void setSetToState(String state);
896
897        int getState() {
898            return _setToState;
899        }
900
901        void setState(int state) {
902            _setToState = state;
903        }
904
905    }
906
907    /**
908     * Element containing Signal Group configuration information
909     * for a Control Sensor as Conditional.
910     */
911    private class SignalGroupSensor extends SignalGroupElement {
912
913        /**
914         * Create a Sensor item for this Signal Head by the name of the Control Sensor
915         * @param sysName system name for new signal group sensor
916         * @param userName user name for new signal group sensor
917         */
918        SignalGroupSensor(String sysName, String userName) {
919            super(sysName, userName);
920        }
921
922        /**
923         * Get the configured On state for the Control Sensor Conditional to be True.
924         *
925         * @return A string describing the On state for use in the GUI
926         * (read from a Properties file, localizable)
927         */
928        @Override
929        String getSetToState() {
930            switch (_setToState) {
931                case Sensor.INACTIVE:
932                    return SET_TO_INACTIVE;
933                case Sensor.ACTIVE:
934                    return SET_TO_ACTIVE;
935                default:
936                    // fall through
937                    break;
938            }
939            return "";
940        }
941
942        /**
943         * Store a uniform value for the On state of the Control Sensor Conditional.
944         * <p>
945         * Pairs should correspond with values in getSetToState()
946         *
947         * @param state Choice from the comboBox, localizable i.e. Active
948         */
949        @Override
950        void setSetToState(String state) {
951            if (SET_TO_INACTIVE.equals(state)) {
952                _setToState = Sensor.INACTIVE;
953            } else if (SET_TO_ACTIVE.equals(state)) {
954                _setToState = Sensor.ACTIVE;
955            }
956        }
957
958        /**
959         * Get the Sensor object.
960         *
961         * @return The Sensor Bean acting as Control Sensor for this Head and Group
962         */
963        Sensor getSensor() {
964            return InstanceManager.getDefault(SensorManager.class).getSensor(_sysName);
965        }
966    }
967
968    /**
969     * Element containing Signal Group configuration information
970     * for a Control Turnout as Conditional.
971     */
972    private class SignalGroupTurnout extends SignalGroupElement {
973
974        /**
975         * Create a Turnout item for this Signal Head by the name of the Control Turnout.
976         *
977         * @param sysName system name for new signal group turnout
978         * @param userName user name for new signal group turnout
979         */
980        SignalGroupTurnout(String sysName, String userName) {
981            super(sysName, userName);
982        }
983
984        /**
985         * Get the configured On state for the Control Turnout Conditional to be True.
986         *
987         * @return A string describing the On state for use in the GUI
988         */
989        @Override
990        String getSetToState() {
991            switch (_setToState) {
992                case Turnout.CLOSED:
993                    return SET_TO_CLOSED;
994                case Turnout.THROWN:
995                    return SET_TO_THROWN;
996                default:
997                    // fall through
998                    break;
999            }
1000            return "";
1001        }
1002
1003        /**
1004         * Store a uniform value for the On state of the Control Sensor Conditional.
1005         * Pairs should correspond with values in getSetToState().
1006         *
1007         * @param state Choice from the comboBox, localizable i.e. Thrown.
1008         */
1009        @Override
1010        void setSetToState(String state) {
1011            if (SET_TO_CLOSED.equals(state)) {
1012                _setToState = Turnout.CLOSED;
1013            } else if (SET_TO_THROWN.equals(state)) {
1014                _setToState = Turnout.THROWN;
1015            }
1016        }
1017
1018        /**
1019         * Get the Turnout object.
1020         *
1021         * @return The Turnout Bean acting as Control Turnout for this Head and Group
1022         */
1023        Turnout getTurnout() {
1024            return InstanceManager.getDefault(TurnoutManager.class).getTurnout(_sysName);
1025        }
1026    }
1027
1028    private final static Logger log = LoggerFactory.getLogger(SignalGroupSubTableAction.class);
1029
1030}