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