001package jmri.jmrit.beantable;
002
003import java.awt.*;
004import java.awt.event.ItemEvent;
005import java.beans.PropertyVetoException;
006import java.io.PrintWriter;
007import java.io.StringWriter;
008import java.util.HashMap;
009import java.util.Map;
010
011import javax.annotation.Nonnull;
012import javax.swing.*;
013import javax.swing.table.TableCellEditor;
014import javax.swing.table.TableColumn;
015import javax.swing.tree.TreeCellEditor;
016
017import jmri.*;
018import jmri.jmrit.logixng.*;
019import jmri.jmrit.logixng.tools.swing.AbstractLogixNGEditor;
020import jmri.jmrit.logixng.tools.swing.LogixNGEditor;
021import jmri.util.JmriJFrame;
022import jmri.util.swing.TriStateJCheckBox;
023import jmri.util.swing.XTableColumnModel;
024
025import org.apache.commons.lang3.mutable.MutableInt;
026
027/**
028 * Swing action to create and register a LogixNG Table.
029 * <p>
030 Also contains the panes to create, edit, and delete a LogixNG.
031 <p>
032 * Most of the text used in this GUI is in BeanTableBundle.properties, accessed
033 * via Bundle.getMessage().
034 *
035 * @author Dave Duchamp Copyright (C) 2007 (LogixTableAction)
036 * @author Pete Cressman Copyright (C) 2009, 2010, 2011 (LogixTableAction)
037 * @author Matthew Harris copyright (c) 2009 (LogixTableAction)
038 * @author Dave Sand copyright (c) 2017 (LogixTableAction)
039 * @author Daniel Bergqvist copyright (c) 2019
040 * @author Dave Sand copyright (c) 2021
041 */
042public class LogixNGTableAction extends AbstractLogixNGTableAction<LogixNG> {
043
044    /**
045     * Create a LogixNGTableAction instance.
046     *
047     * @param s the Action title, not the title of the resulting frame. Perhaps
048     *          this should be changed?
049     */
050    public LogixNGTableAction(String s) {
051        super(s);
052    }
053
054    /**
055     * Create a LogixNGTableAction instance with default title.
056     */
057    public LogixNGTableAction() {
058        this(Bundle.getMessage("TitleLogixNGTable"));
059    }
060
061    @Override
062    protected void createModel() {
063        m = new TableModel();
064        m.setFilter((LogixNG t) -> !t.isInline());
065    }
066
067    @Override
068    protected AbstractLogixNGEditor<LogixNG> getEditor(BeanTableDataModel<LogixNG> m, String sName) {
069        return new LogixNGEditor(m, sName);
070    }
071
072    @Override
073    protected Manager<LogixNG> getManager() {
074        return InstanceManager.getDefault(LogixNG_Manager.class);
075    }
076
077    @Override
078    protected void setEnabled(LogixNG logixNG, boolean enable) {
079        logixNG.setEnabled(enable);
080    }
081
082    @Override
083    protected boolean isEnabled(LogixNG logixNG) {
084        return logixNG.isEnabled();
085    }
086
087    @Override
088    protected void enableAll(boolean enable) {
089        for (LogixNG x : getManager().getNamedBeanSet()) {
090            x.setEnabled(enable);
091        }
092        m.fireTableDataChanged();
093    }
094
095    @Override
096    protected LogixNG createBean(String userName) {
097        LogixNG logixNG =
098                InstanceManager.getDefault(LogixNG_Manager.class)
099                        .createLogixNG(userName);
100        logixNG.activate();
101        logixNG.setEnabled(true);
102        logixNG.clearStartup();
103        return logixNG;
104    }
105
106    @Override
107    protected LogixNG createBean(String systemName, String userName) {
108        LogixNG logixNG =
109                InstanceManager.getDefault(LogixNG_Manager.class)
110                        .createLogixNG(systemName, userName);
111        logixNG.activate();
112        logixNG.setEnabled(true);
113        logixNG.clearStartup();
114        return logixNG;
115    }
116
117    @Override
118    public void deleteBean(LogixNG logixNG) {
119        logixNG.setEnabled(false);
120        try {
121            InstanceManager.getDefault(LogixNG_Manager.class).deleteBean(logixNG, "DoDelete");
122        } catch (PropertyVetoException e) {
123            //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto
124            log.error("{} : Could not Delete.", e.getMessage());
125        }
126    }
127
128    private void copyConditionalNGToLogixNG(
129            @Nonnull ConditionalNG sourceConditionalNG,
130            @Nonnull LogixNG targetBean) {
131
132            // Create ConditionalNG
133            String sysName = InstanceManager.getDefault(ConditionalNG_Manager.class).getAutoSystemName();
134            String oldUserName = sourceConditionalNG.getUserName();
135            String userName = oldUserName != null ? Bundle.getMessage("CopyOfConditionalNG", oldUserName) : null;
136            ConditionalNG targetConditionalNG =
137                    InstanceManager.getDefault(ConditionalNG_Manager.class)
138                            .createConditionalNG(targetBean, sysName, userName);
139
140            sourceConditionalNG.getFemaleSocket().unregisterListeners();
141            targetConditionalNG.getFemaleSocket().unregisterListeners();
142            Map<String, String> systemNames = new HashMap<>();
143            Map<String, String> userNames = new HashMap<>();
144            try {
145                FemaleSocket femaleSourceSocket = sourceConditionalNG.getFemaleSocket();
146                if (femaleSourceSocket.isConnected()) {
147                    targetConditionalNG.getFemaleSocket().connect(
148                            (MaleSocket) femaleSourceSocket.getConnectedSocket()
149                                    .getDeepCopy(systemNames, userNames));
150                }
151            } catch (JmriException ex) {
152                log.error("Could not Copy ConditionalNG.", ex);
153            }
154            sourceConditionalNG.getFemaleSocket().registerListeners();
155            targetConditionalNG.getFemaleSocket().registerListeners();
156    }
157
158    @Override
159    protected void copyBean(@Nonnull LogixNG sourceBean, @Nonnull LogixNG targetBean) {
160        for (int i = 0; i < sourceBean.getNumConditionalNGs(); i++) {
161            copyConditionalNGToLogixNG(sourceBean.getConditionalNG(i), targetBean);
162        }
163    }
164
165    @Override
166    protected boolean isCopyBeanSupported() {
167        return true;
168    }
169
170    @Override
171    protected boolean isExecuteSupported() {
172        return true;
173    }
174
175    @Override
176    protected void execute(@Nonnull LogixNG logixNG) {
177        if (!logixNG.isActivated() && !logixNG.isEnabled()) {
178            JOptionPane.showMessageDialog(f, Bundle.getMessage("LogixNG_CantExecuteLogixNG_InactiveAndNotEnabled"), Bundle.getMessage("LogixNG_Error"), JOptionPane.ERROR_MESSAGE);
179        } else if (!logixNG.isActivated()) {
180            JOptionPane.showMessageDialog(f, Bundle.getMessage("LogixNG_CantExecuteLogixNG_Inactive"), Bundle.getMessage("LogixNG_Error"), JOptionPane.ERROR_MESSAGE);
181        } else if (!logixNG.isEnabled()) {
182            JOptionPane.showMessageDialog(f, Bundle.getMessage("LogixNG_CantExecuteLogixNG_NotEnabled"), Bundle.getMessage("LogixNG_Error"), JOptionPane.ERROR_MESSAGE);
183        } else {
184            logixNG.execute();
185        }
186    }
187
188    @Override
189    protected String getBeanText(LogixNG e) {
190        StringWriter writer = new StringWriter();
191        _curNamedBean.printTree(_printTreeSettings, new PrintWriter(writer), "    ", new MutableInt(0));
192        return writer.toString();
193    }
194
195    @Override
196    protected String getBrowserTitle() {
197        return Bundle.getMessage("LogixNG_Browse_Title");
198    }
199
200    @Override
201    protected String getAddTitleKey() {
202        return "TitleAddLogixNG";
203    }
204
205    @Override
206    protected String getCreateButtonHintKey() {
207        return "LogixNGCreateButtonHint";
208    }
209
210    /**
211     * Create or copy bean frame.
212     *
213     * @param titleId   property key to fetch as title of the frame (using Bundle)
214     * @param startMessageId part 1 of property key to fetch as user instruction on
215     *                  pane, either 1 or 2 is added to form the whole key
216     * @return the button JPanel
217     */
218    @Override
219    protected JPanel makeAddFrame(String titleId, String startMessageId) {
220        addLogixNGFrame = new JmriJFrame(Bundle.getMessage(titleId));
221        addLogixNGFrame.addHelpMenu(
222                "package.jmri.jmrit.beantable.LogixNGTable", true);     // NOI18N
223        addLogixNGFrame.setLocation(50, 30);
224        Container contentPane = addLogixNGFrame.getContentPane();
225        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
226
227        JPanel p;
228        p = new JPanel();
229        p.setLayout(new FlowLayout());
230        p.setLayout(new java.awt.GridBagLayout());
231        java.awt.GridBagConstraints c = new java.awt.GridBagConstraints();
232        c.gridwidth = 1;
233        c.gridheight = 1;
234        c.gridx = 0;
235        c.gridy = 0;
236        c.anchor = java.awt.GridBagConstraints.EAST;
237        p.add(_sysNameLabel, c);
238        _sysNameLabel.setLabelFor(_systemName);
239        c.gridy = 1;
240        p.add(_userNameLabel, c);
241        _userNameLabel.setLabelFor(_addUserName);
242        c.gridx = 1;
243        c.gridy = 0;
244        c.anchor = java.awt.GridBagConstraints.WEST;
245        c.weightx = 1.0;
246        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
247        p.add(_systemName, c);
248        c.gridy = 1;
249        p.add(_addUserName, c);
250        c.gridx = 2;
251        c.gridy = 1;
252        c.anchor = java.awt.GridBagConstraints.WEST;
253        c.weightx = 1.0;
254        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
255        c.gridy = 0;
256        p.add(_autoSystemName, c);
257        _addUserName.setToolTipText(Bundle.getMessage("LogixNGUserNameHint"));    // NOI18N
258        _systemName.setToolTipText(Bundle.getMessage("LogixNGSystemNameHint"));   // NOI18N
259        contentPane.add(p);
260        // set up message
261        JPanel panel3 = new JPanel();
262        panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS));
263        JPanel panel31 = new JPanel();
264        panel31.setLayout(new FlowLayout());
265        JLabel message1 = new JLabel(Bundle.getMessage(startMessageId + "LogixNGMessage1"));  // NOI18N
266        panel31.add(message1);
267        JPanel panel32 = new JPanel();
268        JLabel message2 = new JLabel(Bundle.getMessage(startMessageId + "LogixNGMessage2"));  // NOI18N
269        panel32.add(message2);
270        panel3.add(panel31);
271        panel3.add(panel32);
272        contentPane.add(panel3);
273
274        // set up create and cancel buttons
275        JPanel panel5 = new JPanel();
276        panel5.setLayout(new FlowLayout());
277        // Cancel
278        JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));    // NOI18N
279        panel5.add(cancel);
280        cancel.addActionListener(this::cancelAddPressed);
281        cancel.setToolTipText(Bundle.getMessage("CancelLogixNGButtonHint"));      // NOI18N
282
283        addLogixNGFrame.addWindowListener(new java.awt.event.WindowAdapter() {
284            @Override
285            public void windowClosing(java.awt.event.WindowEvent e) {
286                cancelAddPressed(null);
287            }
288        });
289        contentPane.add(panel5);
290
291        _autoSystemName.addItemListener((ItemEvent e) -> {
292            autoSystemName();
293        });
294        return panel5;
295    }
296
297    @Override
298    protected void getListenerRefsIncludingChildren(LogixNG logixNG, java.util.List<String> list) {
299        logixNG.getListenerRefsIncludingChildren(list);
300    }
301
302    @Override
303    protected boolean hasChildren(LogixNG logixNG) {
304        return logixNG.getNumConditionalNGs() > 0;
305    }
306
307
308    protected class TableModel extends AbstractLogixNGTableAction<LogixNG>.TableModel {
309
310        // overlay the state column with the edit column
311        static public final int STARTUP_COL = NUMCOLUMN;
312
313        /** {@inheritDoc} */
314        @Override
315        public void configureTable(JTable table) {
316            super.configureTable(table);
317
318            table.setDefaultRenderer(TriStateJCheckBox.State.class, new EnablingTriStateCheckboxRenderer());
319
320            TriStateJCheckBox startupCheckBox = new TriStateJCheckBox();
321            TableColumn col = ((XTableColumnModel)table.getColumnModel())
322                    .getColumnByModelIndex(TableModel.STARTUP_COL);
323            col.setCellEditor(new CellEditor(startupCheckBox));
324        }
325
326        /** {@inheritDoc} */
327        @Override
328        public int getColumnCount() {
329            return STARTUP_COL + 1;
330        }
331
332        @Override
333        public String getColumnName(int col) {
334            if (col == STARTUP_COL) {
335                return Bundle.getMessage("ColumnLogixNGStartup");
336            }
337            return super.getColumnName(col);
338        }
339
340        @Override
341        public Class<?> getColumnClass(int col) {
342            if (col == STARTUP_COL) {
343                return TriStateJCheckBox.State.class;
344            }
345            return super.getColumnClass(col);
346        }
347
348        @Override
349        public int getPreferredWidth(int col) {
350            // override default value for SystemName and UserName columns
351            if (col == STARTUP_COL) {
352                return new JTextField(5).getPreferredSize().width;
353            }
354            return super.getPreferredWidth(col);
355        }
356
357        @Override
358        public boolean isCellEditable(int row, int col) {
359            if (col == STARTUP_COL) {
360                return true;
361            }
362            return super.isCellEditable(row, col);
363        }
364
365        @SuppressWarnings("unchecked")  // Unchecked cast from Object to E
366        @Override
367        public Object getValueAt(int row, int col) {
368            if (col == STARTUP_COL) {
369                LogixNG x = (LogixNG) getValueAt(row, SYSNAMECOL);
370                if (x == null) {
371                    return null;
372                }
373                boolean anyTrue = false;
374                boolean anyFalse = false;
375                for (int i=0; i < x.getNumConditionalNGs(); i++) {
376                    ConditionalNG cng = x.getConditionalNG(i);
377                    if (cng.isExecuteAtStartup()) anyTrue = true;
378                    else anyFalse = true;
379                }
380                if (anyTrue && anyFalse) {
381                    return TriStateJCheckBox.State.PARTIAL;
382                } else if (anyTrue) {
383                    return TriStateJCheckBox.State.CHECKED;
384                } else {
385                    return TriStateJCheckBox.State.UNCHECKED;
386                }
387            } else {
388                return super.getValueAt(row, col);
389            }
390        }
391
392        @SuppressWarnings("unchecked")  // Unchecked cast from Object to E
393        @Override
394        public void setValueAt(Object value, int row, int col) {
395            if (col == STARTUP_COL) {
396                // alternate
397                LogixNG x = (LogixNG) getValueAt(row, SYSNAMECOL);
398
399                for (int i=0; i < x.getNumConditionalNGs(); i++) {
400                    ConditionalNG cng = x.getConditionalNG(i);
401                    cng.setExecuteAtStartup(value == TriStateJCheckBox.State.CHECKED);
402                }
403            } else {
404                super.setValueAt(value, row, col);
405            }
406        }
407
408        @Override
409        public String getValue(String s) {
410            return "";
411        }
412    }
413
414    private static class CellEditor extends AbstractCellEditor
415            implements TableCellEditor, TreeCellEditor {
416
417        TriStateJCheckBox _tristateCheckBox;
418
419        public CellEditor(TriStateJCheckBox tristateCheckBox) {
420            this._tristateCheckBox = tristateCheckBox;
421        }
422
423        @Override
424        public Object getCellEditorValue() {
425            return _tristateCheckBox.getState();
426        }
427
428        @Override
429        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
430            return _tristateCheckBox;
431        }
432
433        @Override
434        public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
435            return _tristateCheckBox;
436        }
437
438    }
439
440    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNGTableAction.class);
441
442}