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