001package jmri.jmrit.sensorgroup;
002
003import java.awt.BorderLayout;
004import java.util.ArrayList;
005import java.util.List;
006
007import javax.swing.*;
008import javax.swing.table.TableColumn;
009import javax.swing.table.TableColumnModel;
010import javax.swing.table.TableRowSorter;
011
012import jmri.*;
013import jmri.implementation.DefaultConditionalAction;
014import jmri.implementation.SensorGroupConditional;
015import jmri.swing.RowSorterUtil;
016import jmri.util.swing.JmriJOptionPane;
017
018/**
019 * User interface for creating and editing sensor groups.
020 * <p>
021 * Sensor groups are implemented by (groups) of Routes, not by any other object.
022 *
023 * @author Bob Jacobsen Copyright (C) 2007
024 * @author Pete Cressman Copyright (C) 2009
025 */
026public class SensorGroupFrame extends jmri.util.JmriJFrame {
027
028    public SensorGroupFrame() {
029        super();
030    }
031
032    private static final String NAME_PREFIX = "SENSOR GROUP:";  // should be upper case
033    private static final String NAME_DIVIDER = ":";
034    public static final String logixSysName;
035    public static final String logixUserName = "System Logix";
036    public static final String ConditionalSystemPrefix;
037    private static final String CONDITIONAL_USER_PREFIX = "Sensor Group ";
038    private int rowHeight;
039
040    static {
041        String logixPrefix = InstanceManager.getDefault(LogixManager.class).getSystemNamePrefix();
042        logixSysName = logixPrefix + ":SYS";
043        ConditionalSystemPrefix = logixSysName + "_SGC_";
044    }
045
046    private SensorTableModel _sensorModel;
047    private JScrollPane _sensorScrollPane;
048    private JTextField _nameField;
049    private JList<String> _sensorGroupList;
050
051    @Override
052    public void initComponents() {
053        addHelpMenu("package.jmri.jmrit.sensorgroup.SensorGroupFrame", true);
054
055        setTitle(Bundle.getMessage("Title"));
056        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
057
058        // add the sensor table
059        JPanel p2xs = new JPanel();
060
061        JPanel p21s = new JPanel();
062        p21s.setLayout(new BoxLayout(p21s, BoxLayout.Y_AXIS));
063        p21s.add(new JLabel(Bundle.getMessage("SensorTableLabel1")));
064        p21s.add(new JLabel(Bundle.getMessage("SensorTableLabel2")));
065        p21s.add(new JLabel(Bundle.getMessage("SensorTableLabel3")));
066        p21s.add(new JLabel(Bundle.getMessage("SensorTableLabel4")));
067        p2xs.add(p21s);
068        _sensorModel = new SensorTableModel();
069        JTable sensorTable = new JTable(_sensorModel);
070
071        TableRowSorter<SensorTableModel> sorter = new TableRowSorter<>(_sensorModel);
072        sorter.setComparator(SensorTableModel.SNAME_COLUMN, new jmri.util.AlphanumComparator());
073        sorter.setComparator(SensorTableModel.UNAME_COLUMN, new jmri.util.AlphanumComparator());
074        RowSorterUtil.setSortOrder(sorter, SensorTableModel.SNAME_COLUMN, SortOrder.ASCENDING);
075        sensorTable.setRowSorter(sorter);
076
077        sensorTable.setRowSelectionAllowed(false);
078        sensorTable.setPreferredScrollableViewportSize(new java.awt.Dimension(450, 200));
079        TableColumnModel sensorColumnModel = sensorTable.getColumnModel();
080        TableColumn includeColumnS = sensorColumnModel.
081                getColumn(SensorTableModel.INCLUDE_COLUMN);
082        includeColumnS.setResizable(false);
083        includeColumnS.setMinWidth(50);
084        includeColumnS.setMaxWidth(60);
085        TableColumn sNameColumnS = sensorColumnModel.
086                getColumn(SensorTableModel.SNAME_COLUMN);
087        sNameColumnS.setResizable(true);
088        sNameColumnS.setMinWidth(75);
089        sNameColumnS.setPreferredWidth(95);
090        TableColumn uNameColumnS = sensorColumnModel.
091                getColumn(SensorTableModel.UNAME_COLUMN);
092        uNameColumnS.setResizable(true);
093        uNameColumnS.setMinWidth(210);
094        uNameColumnS.setPreferredWidth(260);
095
096        rowHeight = sensorTable.getRowHeight();
097        _sensorScrollPane = new JScrollPane(sensorTable);
098        p2xs.add(_sensorScrollPane, BorderLayout.CENTER);
099        getContentPane().add(p2xs);
100        p2xs.setVisible(true);
101
102        // add name field
103        JPanel p3 = new JPanel();
104        p3.add(new JLabel(Bundle.getMessage("GroupName")));
105        _nameField = new JTextField(20);
106        p3.add(_nameField);
107        getContentPane().add(p3);
108
109        // button
110        JPanel p4 = new JPanel();
111        JButton viewButton = new JButton(Bundle.getMessage("ButtonViewGroup"));
112        viewButton.addActionListener( e -> viewPressed());
113        p4.add(viewButton);
114        JButton addButton = new JButton(Bundle.getMessage("ButtonMakeGroup"));
115        addButton.addActionListener( e -> addPressed());
116        p4.add(addButton);
117        JButton undoButton = new JButton(Bundle.getMessage("ButtonUndoGroup"));
118        undoButton.addActionListener( e -> undoGroupPressed());
119        p4.add(undoButton);
120        getContentPane().add(p4);
121
122        JPanel p5 = new JPanel();
123
124        DefaultListModel<String> groupModel = new DefaultListModel<>();
125        // Look for Sensor group in Route table
126        RouteManager rm = InstanceManager.getDefault(jmri.RouteManager.class);
127        // List<String> routeList = rm.getSystemNameList();
128        int i = 0;
129        for (NamedBean obj : rm.getNamedBeanSet()) {
130            String name = obj.getSystemName();
131            if (name.startsWith(NAME_PREFIX)) {
132                name = name.substring(NAME_PREFIX.length());
133                String group = name.substring(0, name.indexOf(NAME_DIVIDER));
134                String prefix = NAME_PREFIX + group + NAME_DIVIDER;
135                do {
136                    i++;
137                    if (i >= rm.getNamedBeanSet().size()) {
138                        break;
139                    }
140                    name = obj.getSystemName();
141                } while (name.startsWith(prefix));
142                groupModel.addElement(group);
143            }
144            i++;
145        }
146        // Look for Sensor group in Logix
147        Logix logix = getSystemLogix();
148        for (i = 0; i < logix.getNumConditionals(); i++) {
149            String name = logix.getConditionalByNumberOrder(i);
150            if ( name == null ) {
151                log.error("Could not locate Conditional {} for Logix {}", i, logix);
152                continue;
153            }
154            Conditional c = InstanceManager.getDefault(jmri.ConditionalManager.class).getBySystemName(name);
155            String uname = null;
156            if (c !=null) {
157                uname = c.getUserName();
158            }
159            if (uname != null) {
160                groupModel.addElement(uname.substring(CONDITIONAL_USER_PREFIX.length()));
161            }
162        }
163        _sensorGroupList = new JList<>(groupModel);
164        _sensorGroupList.setPrototypeCellValue(CONDITIONAL_USER_PREFIX + "XXXXXXXXXX");
165        _sensorGroupList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
166        _sensorGroupList.setVisibleRowCount(5);
167        JScrollPane scrollPane = new JScrollPane(_sensorGroupList);
168        p5.add(scrollPane);
169        p5.add(Box.createHorizontalStrut(10));
170        JButton doneButton = new JButton(Bundle.getMessage("ButtonDone"));
171        doneButton.addActionListener( e -> donePressed());
172        p5.add(doneButton);
173        getContentPane().add(p5);
174
175        // pack to cause display
176        pack();
177    }
178
179    private void addPressed() {
180        deleteGroup(false);
181        String group = _nameField.getText();
182        if (group == null || group.length() == 0) {
183            JmriJOptionPane.showMessageDialog(this,
184                    Bundle.getMessage("MessageError1"), Bundle.getMessage("ErrorTitle"),
185                    JmriJOptionPane.ERROR_MESSAGE);
186            return;
187        }
188        Logix logix = getSystemLogix();
189        logix.deActivateLogix();
190        String cSystemName = ConditionalSystemPrefix + group;
191        String cUserName = CONDITIONAL_USER_PREFIX + group;
192        // add new Conditional
193        ArrayList<ConditionalVariable> variableList = new ArrayList<>();
194        ArrayList<ConditionalAction> actionList = new ArrayList<>();
195        int count = 0;
196        for (int i = 0; i < _sensorModel.getRowCount(); i++) {
197            if (Boolean.TRUE.equals( _sensorModel.getValueAt(i, BeanTableModel.INCLUDE_COLUMN))) {
198                String sensor = (String) _sensorModel.getValueAt(i, BeanTableModel.UNAME_COLUMN);
199                if (sensor == null || sensor.length() == 0) {
200                    sensor = (String) _sensorModel.getValueAt(i, BeanTableModel.SNAME_COLUMN);
201                }
202                variableList.add(new ConditionalVariable(false, Conditional.Operator.OR,
203                        Conditional.Type.SENSOR_ACTIVE, sensor, true));
204                actionList.add(new DefaultConditionalAction(Conditional.ACTION_OPTION_ON_CHANGE_TO_TRUE,
205                        Conditional.Action.SET_SENSOR, sensor,
206                        Sensor.INACTIVE, ""));
207                count++;
208            }
209        }
210        if (count < 2) {
211            JmriJOptionPane.showMessageDialog(this,
212                    Bundle.getMessage("MessageError2"), Bundle.getMessage("ErrorTitle"),
213                    JmriJOptionPane.ERROR_MESSAGE);
214        }
215        Conditional c = new SensorGroupConditional(cSystemName, cUserName);
216        c.setStateVariables(variableList);
217        c.setLogicType(Conditional.AntecedentOperator.ALL_OR, "");
218        c.setAction(actionList);
219        logix.addConditional(cSystemName, 0);       // Update the Logix Conditional names list
220        logix.addConditional(cSystemName, c);       // Update the Logix Conditional hash map
221        logix.setEnabled(true);
222        logix.activateLogix();
223        ((DefaultListModel<String>) _sensorGroupList.getModel()).addElement(
224                cUserName.substring(CONDITIONAL_USER_PREFIX.length()));
225        clear();
226    }
227
228    private void viewPressed() {
229        for (int i = 0; i < _sensorModel.getRowCount(); i++) {
230            _sensorModel.setValueAt(Boolean.FALSE, i, BeanTableModel.INCLUDE_COLUMN);
231        }
232        // look for name in List panel
233        String group = _sensorGroupList.getSelectedValue();
234        if (group == null) { // not there, look in text field
235            group = _nameField.getText();
236        }
237        _nameField.setText(group);
238        // Look for Sensor group in Route table
239        RouteManager rm = InstanceManager.getDefault(jmri.RouteManager.class);
240        String prefix = (NAME_PREFIX + group + NAME_DIVIDER);
241        boolean isRoute = false;
242        int setRow = 0;
243        for (Route r : rm.getNamedBeanSet()) {
244            String name = r.getSystemName();
245            if (name.startsWith(prefix)) {
246                isRoute = true;
247                String sensor = name.substring(prefix.length());
248                // find and check that sensor
249                for (int j = _sensorModel.getRowCount() - 1; j >= 0; j--) {
250                    if (_sensorModel.getValueAt(j, BeanTableModel.SNAME_COLUMN).equals(sensor)) {
251                        _sensorModel.setValueAt(Boolean.TRUE, j, BeanTableModel.INCLUDE_COLUMN);
252                        setRow = j;
253                    }
254                }
255            }
256        }
257
258        // look for  Sensor group in SYSTEM Logix
259        if (!isRoute) {
260            Logix logix = getSystemLogix();
261            String cSystemName = (ConditionalSystemPrefix + group);
262            String cUserName = CONDITIONAL_USER_PREFIX + group;
263            for (int i = 0; i < logix.getNumConditionals(); i++) {
264                String name = logix.getConditionalByNumberOrder(i);
265                if (cSystemName.equalsIgnoreCase(name) || cUserName.equals(name)) {     // Ignore case for compatibility
266                    Conditional c = InstanceManager.getDefault(jmri.ConditionalManager.class).getBySystemName(name);
267                    if (c == null) {
268                        log.error("Conditional \"{}\" expected but NOT found in Logix {}", name, logix.getSystemName());
269                    } else {
270                        List<ConditionalVariable> variableList = c.getCopyOfStateVariables();
271                        for (int k = 0; k < variableList.size(); k++) {
272                            String sensor = variableList.get(k).getName();
273                            if (sensor != null) {
274                                for (int j = _sensorModel.getRowCount() - 1; j >= 0; j--) {
275                                    if (sensor.equals(_sensorModel.getValueAt(j, BeanTableModel.UNAME_COLUMN))
276                                            || sensor.equals(_sensorModel.getValueAt(j, BeanTableModel.SNAME_COLUMN))) {
277                                        _sensorModel.setValueAt(Boolean.TRUE, j, BeanTableModel.INCLUDE_COLUMN);
278                                        setRow = j;
279                                    }
280                                }
281                            }
282                        }
283                    }
284                }
285            }
286        }
287        _sensorModel.fireTableDataChanged();
288        setRow -= 9;
289        if (setRow < 0) {
290            setRow = 0;
291        }
292        _sensorScrollPane.getVerticalScrollBar().setValue(setRow * rowHeight);
293    }
294
295    private Logix getSystemLogix() {
296        Logix logix = InstanceManager.getDefault(LogixManager.class).getBySystemName(logixSysName);
297        if (logix == null) {
298            logix = InstanceManager.getDefault(LogixManager.class).createNewLogix(logixSysName, logixUserName);
299        }
300        return logix;
301    }
302
303    private void clear() {
304        _sensorGroupList.getSelectionModel().clearSelection();
305        _nameField.setText("");
306        for (int i = 0; i < _sensorModel.getRowCount(); i++) {
307            _sensorModel.setValueAt(Boolean.FALSE, i, BeanTableModel.INCLUDE_COLUMN);
308        }
309        _sensorModel.fireTableDataChanged();
310    }
311
312    private void donePressed() {
313        _sensorModel.dispose();
314        dispose();
315    }
316
317    private void deleteGroup(boolean showMsg) {
318        String group = _nameField.getText();
319
320        if (group == null || group.isEmpty()) {
321            if (showMsg) {
322                JmriJOptionPane.showMessageDialog(this,
323                        Bundle.getMessage("MessageError3"), Bundle.getMessage("ErrorTitle"),
324                        JmriJOptionPane.ERROR_MESSAGE);
325            }
326            return;
327        }
328
329        // remove the old routes
330        String prefix = (NAME_PREFIX + group + NAME_DIVIDER);
331        RouteManager rm = InstanceManager.getDefault(RouteManager.class);
332        for (Route r : rm.getNamedBeanSet()) {
333            String name = r.getSystemName();
334            if (name.startsWith(prefix)) {
335                // OK, kill this one
336                r.deActivateRoute();
337                rm.deleteRoute(r);
338            }
339        }
340
341        // remove Logix IX:SYS conditional
342        // Due to a change at 4.17.2, the system names are no longer forced to upper case.
343        // Older SYS conditionals will have an upper case name, so the user name is an alternate name.
344        Logix logix = getSystemLogix();
345        String cSystemName = ConditionalSystemPrefix + group;
346        String cUserName = CONDITIONAL_USER_PREFIX + group;
347
348        for (int i = 0; i < logix.getNumConditionals(); i++) {
349            String cdlName = logix.getConditionalByNumberOrder(i);
350            Conditional cdl = logix.getConditional(cdlName);
351            if (cdl != null) {
352                if (cSystemName.equals(cdl.getSystemName()) || cUserName.equals(cdl.getUserName())) {
353                    String[] msgs = logix.deleteConditional(cdlName);
354                    if (msgs == null) {
355                        break;
356                    }
357
358                    // Conditional delete failed or was vetoed
359                    if (showMsg) {
360                        JmriJOptionPane.showMessageDialog(this,
361                                Bundle.getMessage("MessageError41") + " " + msgs[0] + " (" + msgs[1] + ") "
362                                + Bundle.getMessage("MessageError42") + " " + msgs[2] + " (" + msgs[3] + "), "
363                                + Bundle.getMessage("MessageError43") + " " + msgs[4] + " (" + msgs[5] + "). "
364                                + Bundle.getMessage("MessageError44"),
365                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
366                    }
367                    return;
368                }
369            } else {
370                log.error("Conditional \"{}\" expected but NOT found in Logix {}", cdlName, logix.getSystemName());
371                return;
372            }
373        }
374
375        // remove from list
376        DefaultListModel<String> model = (DefaultListModel<String>) _sensorGroupList.getModel();
377        model.removeElement(group);
378    }
379
380    private void undoGroupPressed() {
381        deleteGroup(true);
382        clear();
383    }
384
385    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SensorGroupFrame.class);
386}