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