001package jmri.jmrit.logixng.tools.swing;
002
003import java.awt.*;
004import java.awt.event.ActionEvent;
005import java.awt.event.ItemEvent;
006import java.awt.event.KeyEvent;
007import java.beans.PropertyChangeListener;
008import java.beans.PropertyVetoException;
009import java.text.MessageFormat;
010import java.util.*;
011import java.util.List;
012import java.util.concurrent.atomic.AtomicBoolean;
013
014import javax.swing.*;
015import javax.swing.border.Border;
016import javax.swing.table.AbstractTableModel;
017import javax.swing.table.TableCellEditor;
018import javax.swing.table.TableColumn;
019import javax.swing.table.TableColumnModel;
020
021import jmri.*;
022import jmri.jmrit.beantable.BeanTableDataModel;
023import jmri.jmrit.logixng.*;
024import jmri.jmrit.logixng.util.LogixNG_Thread;
025import jmri.util.JmriJFrame;
026import jmri.util.swing.JmriJOptionPane;
027import jmri.util.table.ButtonEditor;
028import jmri.util.table.ButtonRenderer;
029
030/**
031 * Editor for LogixNG
032 *
033 * @author Dave Duchamp Copyright (C) 2007  (ConditionalListEdit)
034 * @author Pete Cressman Copyright (C) 2009, 2010, 2011  (ConditionalListEdit)
035 * @author Matthew Harris copyright (c) 2009  (ConditionalListEdit)
036 * @author Dave Sand copyright (c) 2017  (ConditionalListEdit)
037 * @author Daniel Bergqvist (c) 2019
038 * @author Dave Sand (c) 2021
039 */
040public final class LogixNGEditor implements AbstractLogixNGEditor<LogixNG> {
041
042    BeanTableDataModel<LogixNG> beanTableDataModel;
043
044    LogixNG_Manager _logixNG_Manager = null;
045    LogixNG _curLogixNG = null;
046
047    ConditionalNGEditor _treeEdit = null;
048    ConditionalNGDebugger _debugger = null;
049
050    int _numConditionalNGs = 0;
051    boolean _inEditMode = false;
052
053    boolean _showReminder = false;
054    boolean _suppressReminder = false;
055    boolean _suppressIndirectRef = false;
056    private boolean _checkEnabled = jmri.InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).isStoreCheckEnabled();
057
058    private final JCheckBox _autoSystemName = new JCheckBox(Bundle.getMessage("LabelAutoSysName"));   // NOI18N
059    private final JLabel _sysNameLabel = new JLabel(Bundle.getMessage("SystemName") + ":");  // NOI18N
060    private final JLabel _userNameLabel = new JLabel(Bundle.getMessage("UserName") + ":");   // NOI18N
061    private final String systemNameAuto = this.getClass().getName() + ".AutoSystemName";         // NOI18N
062    private final JTextField _systemName = new JTextField(20);
063    private final JTextField _addUserName = new JTextField(20);
064
065
066    /**
067     * Create a new ConditionalNG List View editor.
068     *
069     * @param m the bean table model
070     * @param sName name of the LogixNG being edited
071     */
072    public LogixNGEditor(BeanTableDataModel<LogixNG> m, String sName) {
073        this.beanTableDataModel = m;
074        _logixNG_Manager = InstanceManager.getDefault(jmri.jmrit.logixng.LogixNG_Manager.class);
075        _curLogixNG = _logixNG_Manager.getBySystemName(sName);
076        makeEditLogixNGWindow();
077    }
078
079    // ------------ LogixNG Variables ------------
080    JmriJFrame _editLogixNGFrame = null;
081    JTextField editUserName = new JTextField(20);
082    JLabel status = new JLabel(" ");
083
084    // ------------ ConditionalNG Variables ------------
085    private ConditionalNGTableModel _conditionalNGTableModel = null;
086    private JCheckBox _showStartupThreadsCheckBox = null;
087    private ConditionalNG _curConditionalNG = null;
088    int _conditionalRowNumber = 0;
089    boolean _inReorderMode = false;
090    boolean _inActReorder = false;
091    boolean _inVarReorder = false;
092    int _nextInOrder = 0;
093
094    // ------------ Select LogixNG/ConditionalNG Variables ------------
095    JPanel _selectLogixNGPanel = null;
096    JPanel _selectConditionalNGPanel = null;
097//    private JComboBox<String> _selectLogixNGComboBox = new JComboBox<>();
098//    private JComboBox<String> _selectConditionalNGComboBox = new JComboBox<>();
099    TreeMap<String, String> _selectLogixNGMap = new TreeMap<>();
100    ArrayList<String> _selectConditionalNGList = new ArrayList<>();
101
102    // ------------ Edit ConditionalNG Variables ------------
103    boolean _inEditConditionalNGMode = false;
104    JmriJFrame _editConditionalNGFrame = null;
105    JRadioButton _triggerOnChangeButton;
106
107    // ------------ Methods for Edit LogixNG Pane ------------
108
109    /**
110     * Create and/or initialize the Edit LogixNG pane.
111     */
112    void makeEditLogixNGWindow() {
113        editUserName.setText(_curLogixNG.getUserName());
114        // clear conditional table if needed
115        if (_conditionalNGTableModel != null) {
116            _conditionalNGTableModel.fireTableStructureChanged();
117        }
118        _inEditMode = true;
119        if (_editLogixNGFrame == null) {
120            if (_curLogixNG.getUserName() != null) {
121                _editLogixNGFrame = new JmriJFrame(
122                        Bundle.getMessage("TitleEditLogixNG2",
123                                _curLogixNG.getSystemName(),   // NOI18N
124                                _curLogixNG.getUserName()),    // NOI18N
125                        false,
126                        false);
127            } else {
128                _editLogixNGFrame = new JmriJFrame(
129                        Bundle.getMessage("TitleEditLogixNG", _curLogixNG.getSystemName()),  // NOI18N
130                        false,
131                        false);
132            }
133            _editLogixNGFrame.addHelpMenu(
134                    "package.jmri.jmrit.logixng.LogixNGTableEditor", true);  // NOI18N
135            _editLogixNGFrame.setLocation(100, 30);
136            Container contentPane = _editLogixNGFrame.getContentPane();
137            contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
138            JPanel panel1 = new JPanel();
139            panel1.setLayout(new FlowLayout());
140            JLabel systemNameLabel = new JLabel(Bundle.getMessage("ColumnSystemName") + ":");  // NOI18N
141            panel1.add(systemNameLabel);
142            JLabel fixedSystemName = new JLabel(_curLogixNG.getSystemName());
143            panel1.add(fixedSystemName);
144            contentPane.add(panel1);
145            JPanel panel2 = new JPanel();
146            panel2.setLayout(new FlowLayout());
147            JLabel userNameLabel = new JLabel(Bundle.getMessage("ColumnUserName") + ":");  // NOI18N
148            panel2.add(userNameLabel);
149            panel2.add(editUserName);
150            editUserName.setToolTipText(Bundle.getMessage("LogixNGUserNameHint2"));  // NOI18N
151            contentPane.add(panel2);
152            // add table of ConditionalNGs
153            JPanel pctSpace = new JPanel();
154            pctSpace.setLayout(new FlowLayout());
155            pctSpace.add(new JLabel("   "));
156            contentPane.add(pctSpace);
157            JPanel pTitle = new JPanel();
158            pTitle.setLayout(new FlowLayout());
159            pTitle.add(new JLabel(Bundle.getMessage("ConditionalNGTableTitle")));  // NOI18N
160            contentPane.add(pTitle);
161            // initialize table of conditionals
162            _conditionalNGTableModel = new ConditionalNGTableModel();
163            JTable conditionalTable = new JTable(_conditionalNGTableModel);
164            conditionalTable.setRowSelectionAllowed(false);
165            TableColumnModel conditionalColumnModel = conditionalTable
166                    .getColumnModel();
167            TableColumn sNameColumn = conditionalColumnModel
168                    .getColumn(ConditionalNGTableModel.SNAME_COLUMN);
169            sNameColumn.setResizable(true);
170            sNameColumn.setMinWidth(100);
171            sNameColumn.setPreferredWidth(130);
172            TableColumn uNameColumn = conditionalColumnModel
173                    .getColumn(ConditionalNGTableModel.UNAME_COLUMN);
174            uNameColumn.setResizable(true);
175            uNameColumn.setMinWidth(210);
176            uNameColumn.setPreferredWidth(260);
177            TableColumn threadColumn = conditionalColumnModel
178                    .getColumn(ConditionalNGTableModel.THREAD_COLUMN);
179            threadColumn.setResizable(true);
180            threadColumn.setMinWidth(210);
181            threadColumn.setPreferredWidth(260);
182            TableColumn buttonColumn = conditionalColumnModel
183                    .getColumn(ConditionalNGTableModel.BUTTON_COLUMN);
184            TableColumn buttonDeleteColumn = conditionalColumnModel
185                    .getColumn(ConditionalNGTableModel.BUTTON_DELETE_COLUMN);
186            TableColumn buttonEditThreadsColumn = conditionalColumnModel
187                    .getColumn(ConditionalNGTableModel.BUTTON_EDIT_THREADS_COLUMN);
188
189            // install button renderer and editor
190            ButtonRenderer buttonRenderer = new ButtonRenderer();
191            conditionalTable.setDefaultRenderer(JButton.class, buttonRenderer);
192            TableCellEditor buttonEditor = new ButtonEditor(new JButton());
193            conditionalTable.setDefaultEditor(JButton.class, buttonEditor);
194            JButton testButton = new JButton("XXXXXX");  // NOI18N
195            JButton testButton2 = new JButton("XXXXXXXXXX");  // NOI18N
196            conditionalTable.setRowHeight(testButton.getPreferredSize().height);
197            buttonColumn.setMinWidth(testButton.getPreferredSize().width);
198            buttonColumn.setMaxWidth(testButton.getPreferredSize().width);
199            buttonColumn.setResizable(false);
200            buttonDeleteColumn.setMinWidth(testButton.getPreferredSize().width);
201            buttonDeleteColumn.setMaxWidth(testButton.getPreferredSize().width);
202            buttonDeleteColumn.setResizable(false);
203            buttonEditThreadsColumn.setMinWidth(testButton2.getPreferredSize().width);
204            buttonEditThreadsColumn.setMaxWidth(testButton2.getPreferredSize().width);
205            buttonEditThreadsColumn.setResizable(false);
206
207            JScrollPane conditionalTableScrollPane = new JScrollPane(conditionalTable);
208            Dimension dim = conditionalTable.getPreferredSize();
209            dim.height = 450;
210            conditionalTableScrollPane.getViewport().setPreferredSize(dim);
211            contentPane.add(conditionalTableScrollPane);
212
213            _showStartupThreadsCheckBox = new JCheckBox(Bundle.getMessage("ShowStartupThreadCheckBox"));
214            contentPane.add(_showStartupThreadsCheckBox);
215            _showStartupThreadsCheckBox.addActionListener((evt) -> {
216                _conditionalNGTableModel.setShowStartupThreads(
217                        _showStartupThreadsCheckBox.isSelected());
218            });
219
220            // add message area between table and buttons
221            JPanel panel4 = new JPanel();
222            panel4.setLayout(new BoxLayout(panel4, BoxLayout.Y_AXIS));
223            JPanel panel41 = new JPanel();
224            panel41.setLayout(new FlowLayout());
225            panel41.add(status);
226            panel4.add(panel41);
227            JPanel panel42 = new JPanel();
228            panel42.setLayout(new FlowLayout());
229            // ConditionalNG panel buttons - New ConditionalNG
230            JButton newConditionalNGButton = new JButton(Bundle.getMessage("NewConditionalNGButton"));  // NOI18N
231            panel42.add(newConditionalNGButton);
232            newConditionalNGButton.addActionListener((e) -> {
233                newConditionalNGPressed(e);
234            });
235            newConditionalNGButton.setToolTipText(Bundle.getMessage("NewConditionalNGButtonHint"));  // NOI18N
236            // ConditionalNG panel buttons - Reorder
237            JButton reorderButton = new JButton(Bundle.getMessage("ReorderButton"));  // NOI18N
238            panel42.add(reorderButton);
239            reorderButton.addActionListener((e) -> {
240                reorderPressed(e);
241            });
242            reorderButton.setToolTipText(Bundle.getMessage("ReorderButtonHint"));  // NOI18N
243            // ConditionalNG panel buttons - Calculate
244            JButton executeButton = new JButton(Bundle.getMessage("ExecuteButton"));  // NOI18N
245            panel42.add(executeButton);
246            executeButton.addActionListener((e) -> {
247                executePressed(e);
248            });
249            executeButton.setToolTipText(Bundle.getMessage("ExecuteButtonHint"));  // NOI18N
250            panel4.add(panel42);
251            Border panel4Border = BorderFactory.createEtchedBorder();
252            panel4.setBorder(panel4Border);
253            contentPane.add(panel4);
254            // add buttons at bottom of window
255            JPanel panel5 = new JPanel();
256            panel5.setLayout(new FlowLayout());
257            // Bottom Buttons - Done LogixNG
258            JButton done = new JButton(Bundle.getMessage("ButtonDone"));  // NOI18N
259            panel5.add(done);
260            done.addActionListener((e) -> {
261                donePressed(e);
262            });
263            done.setToolTipText(Bundle.getMessage("DoneButtonHint"));  // NOI18N
264            // Delete LogixNG
265            JButton delete = new JButton(Bundle.getMessage("ButtonDelete"));  // NOI18N
266            panel5.add(delete);
267            delete.addActionListener((e) -> {
268                deletePressed();
269            });
270            delete.setToolTipText(Bundle.getMessage("DeleteLogixNGButtonHint"));  // NOI18N
271            contentPane.add(panel5);
272        }
273
274        _editLogixNGFrame.addWindowListener(new java.awt.event.WindowAdapter() {
275            @Override
276            public void windowClosing(java.awt.event.WindowEvent e) {
277                if (_inEditMode) {
278                    donePressed(null);
279                } else {
280                    finishDone();
281                }
282            }
283        });
284        _editLogixNGFrame.pack();
285        _editLogixNGFrame.setVisible(true);
286    }
287
288    @Override
289    public void bringToFront() {
290        if (_editLogixNGFrame != null) {
291            _editLogixNGFrame.setVisible(true);
292        }
293    }
294
295    /**
296     * Display reminder to save.
297     */
298    void showSaveReminder() {
299        if (_showReminder && !_checkEnabled) {
300            if (InstanceManager.getNullableDefault(jmri.UserPreferencesManager.class) != null) {
301                InstanceManager.getDefault(jmri.UserPreferencesManager.class).
302                        showInfoMessage(Bundle.getMessage("ReminderTitle"), // NOI18N
303                                Bundle.getMessage("ReminderSaveString", // NOI18N
304                                        Bundle.getMessage("MenuItemLogixNGTable")), // NOI18N
305                                getClassName(),
306                                "remindSaveLogixNG"); // NOI18N
307            }
308        }
309    }
310
311    /**
312     * Respond to the Reorder Button in the Edit LogixNG pane.
313     *
314     * @param e The event heard
315     */
316    void reorderPressed(ActionEvent e) {
317        if (checkEditConditionalNG()) {
318            return;
319        }
320        // Check if reorder is reasonable
321        _showReminder = true;
322        _nextInOrder = 0;
323        _inReorderMode = true;
324        status.setText(Bundle.getMessage("ReorderMessage"));  // NOI18N
325        _conditionalNGTableModel.fireTableDataChanged();
326    }
327
328    /**
329     * Respond to the First/Next (Delete) Button in the Edit LogixNG window.
330     *
331     * @param row index of the row to put as next in line (instead of the one
332     *            that was supposed to be next)
333     */
334    void swapConditionalNG(int row) {
335        _curLogixNG.swapConditionalNG(_nextInOrder, row);
336        _nextInOrder++;
337        if (_nextInOrder >= _numConditionalNGs) {
338            _inReorderMode = false;
339        }
340        //status.setText("");
341        _conditionalNGTableModel.fireTableDataChanged();
342    }
343
344    /**
345     * Responds to the Execute Button in the Edit LogixNG window.
346     *
347     * @param e The event heard
348     */
349    void executePressed(ActionEvent e) {
350        if (checkEditConditionalNG()) {
351            return;
352        }
353        // are there ConditionalNGs to execute?
354        if (_numConditionalNGs > 0) {
355            // There are conditionals to calculate
356            for (int i = 0; i < _numConditionalNGs; i++) {
357                ConditionalNG c = _curLogixNG.getConditionalNG(i);
358                if (c == null) {
359                    log.error("Invalid conditional system name when executing"); // NOI18N
360                } else {
361                    c.execute();
362                }
363            }
364            // force the table to update
365//            conditionalNGTableModel.fireTableDataChanged();
366        }
367    }
368
369    /**
370     * Respond to the Done button in the Edit LogixNG window.
371     * <p>
372     * Note: We also get here if the Edit LogixNG window is dismissed, or if the
373     * Add button is pressed in the Logic Table with an active Edit LogixNG
374     * window.
375     *
376     * @param e The event heard
377     */
378    void donePressed(ActionEvent e) {
379        if (checkEditConditionalNG()) {
380            return;
381        }
382        // Check if the User Name has been changed
383        String uName = editUserName.getText().trim();
384        if (!(uName.equals(_curLogixNG.getUserName()))) {
385            // user name has changed - check if already in use
386            if (uName.length() > 0) {
387                LogixNG p = _logixNG_Manager.getByUserName(uName);
388                if (p != null) {
389                    // LogixNG with this user name already exists
390                    log.error("Failure to update LogixNG with Duplicate User Name: {}", uName); // NOI18N
391                    JmriJOptionPane.showMessageDialog(_editLogixNGFrame,
392                            Bundle.getMessage("Error_UserNameInUse"),
393                            Bundle.getMessage("ErrorTitle"), // NOI18N
394                            JmriJOptionPane.ERROR_MESSAGE);
395                    return;
396                }
397            }
398            // user name is unique, change it
399            // user name is unique, change it
400            logixNG_Data.clear();
401            logixNG_Data.put("chgUname", uName);  // NOI18N
402            fireEditorEvent();
403        }
404        // complete update and activate LogixNG
405        finishDone();
406    }
407
408    void finishDone() {
409        showSaveReminder();
410        _inEditMode = false;
411        if (_editLogixNGFrame != null) {
412            _editLogixNGFrame.setVisible(false);
413            _editLogixNGFrame.dispose();
414            _editLogixNGFrame = null;
415        }
416        logixNG_Data.clear();
417        logixNG_Data.put("Finish", _curLogixNG.getSystemName());   // NOI18N
418        fireEditorEvent();
419    }
420
421    /**
422     * Respond to the Delete button in the Edit LogixNG window.
423     */
424    void deletePressed() {
425        if (checkEditConditionalNG()) {
426            return;
427        }
428
429        _showReminder = true;
430        logixNG_Data.clear();
431        logixNG_Data.put("Delete", _curLogixNG.getSystemName());   // NOI18N
432        fireEditorEvent();
433        finishDone();
434    }
435
436    /**
437     * Respond to the New ConditionalNG Button in Edit LogixNG Window.
438     *
439     * @param e The event heard
440     */
441    void newConditionalNGPressed(ActionEvent e) {
442        if (checkEditConditionalNG()) {
443            return;
444        }
445
446        // make an Add Item Frame
447        if (showAddLogixNGFrame()) {
448            if (_systemName.getText().isEmpty() && _autoSystemName.isSelected()) {
449                _systemName.setText(InstanceManager.getDefault(ConditionalNG_Manager.class).getAutoSystemName());
450            }
451
452            // Create ConditionalNG
453            _curConditionalNG =
454                    InstanceManager.getDefault(ConditionalNG_Manager.class)
455                            .createConditionalNG(_curLogixNG, _systemName.getText(), _addUserName.getText());
456
457            if (_curConditionalNG == null) {
458                // should never get here unless there is an assignment conflict
459                log.error("Failure to create ConditionalNG"); // NOI18N
460                return;
461            }
462            // add to LogixNG at the end of the calculate order
463            _conditionalNGTableModel.fireTableRowsInserted(_numConditionalNGs, _numConditionalNGs);
464            _conditionalRowNumber = _numConditionalNGs;
465            _numConditionalNGs++;
466            _showReminder = true;
467            makeEditConditionalNGWindow();
468        }
469    }
470
471    /**
472     * Create or edit action/expression dialog.
473     */
474    private boolean showAddLogixNGFrame() {
475
476        AtomicBoolean result = new AtomicBoolean(false);
477
478        JDialog dialog  = new JDialog(
479                _editLogixNGFrame,
480                Bundle.getMessage("AddConditionalNGDialogTitle"),
481                true);
482//        frame.addHelpMenu(
483//                "package.jmri.jmrit.logixng.tools.swing.ConditionalNGAddEdit", true);     // NOI18N
484        Container contentPanel = dialog.getContentPane();
485        contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
486
487        JPanel p;
488        p = new JPanel();
489//        p.setLayout(new FlowLayout());
490        p.setLayout(new java.awt.GridBagLayout());
491        java.awt.GridBagConstraints c = new java.awt.GridBagConstraints();
492        c.gridwidth = 1;
493        c.gridheight = 1;
494        c.gridx = 0;
495        c.gridy = 0;
496        c.anchor = java.awt.GridBagConstraints.EAST;
497        p.add(_sysNameLabel, c);
498        c.gridy = 1;
499        p.add(_userNameLabel, c);
500        c.gridx = 1;
501        c.gridy = 0;
502        c.anchor = java.awt.GridBagConstraints.WEST;
503        c.weightx = 1.0;
504        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
505        p.add(_systemName, c);
506        c.gridy = 1;
507        p.add(_addUserName, c);
508        c.gridx = 2;
509        c.gridy = 1;
510        c.anchor = java.awt.GridBagConstraints.WEST;
511        c.weightx = 1.0;
512        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
513        c.gridy = 0;
514        p.add(_autoSystemName, c);
515
516        _systemName.setText("");
517        _systemName.setEnabled(true);
518        _addUserName.setText("");
519
520        _addUserName.setToolTipText(Bundle.getMessage("UserNameHint"));    // NOI18N
521//        _addUserName.setToolTipText("LogixNGUserNameHint");    // NOI18N
522        _systemName.setToolTipText(Bundle.getMessage("LogixNGSystemNameHint"));   // NOI18N
523//        _systemName.setToolTipText("LogixNGSystemNameHint");   // NOI18N
524        contentPanel.add(p);
525        // set up message
526        JPanel panel3 = new JPanel();
527        panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS));
528        JPanel panel31 = new JPanel();
529//        panel31.setLayout(new FlowLayout());
530        JPanel panel32 = new JPanel();
531        JLabel message1 = new JLabel(Bundle.getMessage("AddMessage1"));  // NOI18N
532        panel31.add(message1);
533        JLabel message2 = new JLabel(Bundle.getMessage("AddMessage2"));  // NOI18N
534        panel32.add(message2);
535
536        // set up create and cancel buttons
537        JPanel panel5 = new JPanel();
538        panel5.setLayout(new FlowLayout());
539
540        // Get panel for the item
541        panel3.add(panel31);
542        panel3.add(panel32);
543        contentPanel.add(panel3);
544
545        // Cancel
546        JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));    // NOI18N
547        panel5.add(cancel);
548        cancel.addActionListener((ActionEvent e) -> {
549            dialog.dispose();
550        });
551//        cancel.setToolTipText(Bundle.getMessage("CancelLogixButtonHint"));      // NOI18N
552        cancel.setToolTipText("CancelLogixButtonHint");      // NOI18N
553
554        JButton create = new JButton(Bundle.getMessage("ButtonCreate"));  // NOI18N
555        create.addActionListener((ActionEvent e2) -> {
556            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
557                prefMgr.setCheckboxPreferenceState(systemNameAuto, _autoSystemName.isSelected());
558            });
559            result.set(true);
560            dialog.dispose();
561        });
562        create.setToolTipText(Bundle.getMessage("CreateButtonHint"));  // NOI18N
563
564        panel5.add(create);
565
566        dialog.addWindowListener(new java.awt.event.WindowAdapter() {
567            @Override
568            public void windowClosing(java.awt.event.WindowEvent e) {
569                dialog.dispose();
570            }
571        });
572
573        contentPanel.add(panel5);
574
575        _autoSystemName.addItemListener((ItemEvent e) -> {
576            autoSystemName();
577        });
578//        addLogixNGFrame.setLocationRelativeTo(component);
579        dialog.pack();
580        dialog.setLocationRelativeTo(null);
581
582        _autoSystemName.setSelected(true);
583        InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
584            _autoSystemName.setSelected(prefMgr.getCheckboxPreferenceState(systemNameAuto, true));
585        });
586
587        dialog.setVisible(true);
588
589        return result.get();
590    }
591
592    /**
593     * Enable/disable fields for data entry when user selects to have system
594     * name automatically generated.
595     */
596    void autoSystemName() {
597        if (_autoSystemName.isSelected()) {
598            _systemName.setEnabled(false);
599            _sysNameLabel.setEnabled(false);
600        } else {
601            _systemName.setEnabled(true);
602            _sysNameLabel.setEnabled(true);
603        }
604    }
605
606    // ============ Edit Conditional Window and Methods ============
607
608    /**
609     * Create and/or initialize the Edit Conditional window.
610     * <p>
611     * Note: you can get here via the New Conditional button
612     * (newConditionalPressed) or via an Edit button in the Conditional table of
613     * the Edit Logix window.
614     */
615    void makeEditConditionalNGWindow() {
616        // Create a new LogixNG edit view, add the listener.
617        _treeEdit = new ConditionalNGEditor(_curConditionalNG);
618        _treeEdit.initComponents();
619        _treeEdit.setVisible(true);
620        _inEditConditionalNGMode = true;
621        _editConditionalNGFrame = _treeEdit;
622        _editConditionalNGFrame.addHelpMenu(
623                "package.jmri.jmrit.logixng.ConditionalNGEditor", true);  // NOI18N
624
625        final LogixNGEditor logixNGEditor = this;
626        _treeEdit.addLogixNGEventListener(new LogixNGEventListenerImpl(logixNGEditor));
627    }
628
629    /**
630     * Create and/or initialize the Edit Conditional window.
631     * <p>
632     * Note: you can get here via the New Conditional button
633     * (newConditionalPressed) or via an Edit button in the Conditional table of
634     * the Edit Logix window.
635     */
636    void makeDebugConditionalNGWindow() {
637        // Create a new LogixNG edit view, add the listener.
638        _debugger = new ConditionalNGDebugger(_curConditionalNG);
639        _debugger.initComponents();
640        _debugger.setVisible(true);
641        _inEditConditionalNGMode = true;
642        _editConditionalNGFrame = _debugger;
643
644        final LogixNGEditor logixNGEditor = this;
645        _debugger.addLogixNGEventListener(new LogixNG_DebuggerEventListenerImpl(logixNGEditor));
646    }
647
648    // ------------ Methods for Edit ConditionalNG Pane ------------
649
650    /**
651     * Respond to Edit Button in the ConditionalNG table of the Edit LogixNG Window.
652     *
653     * @param rx index (row number) of ConditionalNG to be edited
654     */
655    void editConditionalNGPressed(int rx) {
656        if (checkEditConditionalNG()) {
657            return;
658        }
659        // get ConditionalNG to edit
660        _curConditionalNG = _curLogixNG.getConditionalNG(rx);
661        if (_curConditionalNG == null) {
662            log.error("Attempted edit of non-existant conditional.");  // NOI18N
663            return;
664        }
665        _conditionalRowNumber = rx;
666        // get action variables
667        makeEditConditionalNGWindow();
668    }
669
670    /**
671     * Respond to Edit Button in the ConditionalNG table of the Edit LogixNG Window.
672     *
673     * @param rx index (row number) of ConditionalNG to be edited
674     */
675    void debugConditionalNGPressed(int rx) {
676        if (checkEditConditionalNG()) {
677            return;
678        }
679        // get ConditionalNG to edit
680        _curConditionalNG = _curLogixNG.getConditionalNG(rx);
681        if (_curConditionalNG == null) {
682            log.error("Attempted edit of non-existant conditional.");  // NOI18N
683            return;
684        }
685        _conditionalRowNumber = rx;
686        // get action variables
687        makeDebugConditionalNGWindow();
688    }
689
690    /**
691     * Check if edit of a conditional is in progress.
692     *
693     * @return true if this is the case, after showing dialog to user
694     */
695    private boolean checkEditConditionalNG() {
696        if (_inEditConditionalNGMode) {
697            // Already editing a ConditionalNG, ask for completion of that edit
698            JmriJOptionPane.showMessageDialog(_editConditionalNGFrame,
699                    Bundle.getMessage("Error_ConditionalNGInEditMode", _curConditionalNG.getSystemName()), // NOI18N
700                    Bundle.getMessage("ErrorTitle"), // NOI18N
701                    JmriJOptionPane.ERROR_MESSAGE);
702            _editConditionalNGFrame.setVisible(true);
703            return true;
704        }
705        return false;
706    }
707
708    boolean checkConditionalNGUserName(String uName, LogixNG logixNG) {
709        if ((uName != null) && (!(uName.equals("")))) {
710            for (int i=0; i < logixNG.getNumConditionalNGs(); i++) {
711                ConditionalNG p = logixNG.getConditionalNG(i);
712                if (uName.equals(p.getUserName())) {
713                    // ConditionalNG with this user name already exists
714                    log.error("Failure to update ConditionalNG with Duplicate User Name: {}", uName); // NOI18N
715                    JmriJOptionPane.showMessageDialog(_editConditionalNGFrame,
716                            Bundle.getMessage("Error10"),    // NOI18N
717                            Bundle.getMessage("ErrorTitle"), // NOI18N
718                            JmriJOptionPane.ERROR_MESSAGE);
719                    return false;
720                }
721            }
722        } // else return false;
723        return true;
724    }
725
726    /**
727     * Check form of ConditionalNG systemName.
728     *
729     * @param sName system name of bean to be checked
730     * @return false if sName is empty string or null
731     */
732    boolean checkConditionalNGSystemName(String sName) {
733        if ((sName != null) && (!(sName.equals("")))) {
734            ConditionalNG p = _curLogixNG.getConditionalNG(sName);
735            if (p != null) {
736                return false;
737            }
738        } else {
739            return false;
740        }
741        return true;
742    }
743
744    // ------------ Table Models ------------
745
746    /**
747     * Table model for ConditionalNGs in the Edit LogixNG pane.
748     */
749    public final class ConditionalNGTableModel extends AbstractTableModel
750            implements PropertyChangeListener {
751
752        public static final int SNAME_COLUMN = 0;
753        public static final int UNAME_COLUMN = SNAME_COLUMN + 1;
754        public static final int THREAD_COLUMN = UNAME_COLUMN + 1;
755        public static final int ENABLED_COLUMN = THREAD_COLUMN + 1;
756        public static final int STARTUP_COLUMN = ENABLED_COLUMN + 1;
757        public static final int BUTTON_COLUMN = STARTUP_COLUMN + 1;
758        public static final int BUTTON_DEBUG_COLUMN = BUTTON_COLUMN + 1;
759        public static final int BUTTON_DELETE_COLUMN = BUTTON_DEBUG_COLUMN + 1;
760        public static final int BUTTON_EDIT_THREADS_COLUMN = BUTTON_DELETE_COLUMN + 1;
761        public static final int NUM_COLUMNS = BUTTON_EDIT_THREADS_COLUMN + 1;
762
763        private boolean _showStartupThreads;
764
765
766        public ConditionalNGTableModel() {
767            super();
768            updateConditionalNGListeners();
769        }
770
771        synchronized void updateConditionalNGListeners() {
772            // first, remove listeners from the individual objects
773            ConditionalNG c;
774            _numConditionalNGs = _curLogixNG.getNumConditionalNGs();
775            for (int i = 0; i < _numConditionalNGs; i++) {
776                // if object has been deleted, it's not here; ignore it
777                c = _curLogixNG.getConditionalNG(i);
778                if (c != null) {
779                    c.removePropertyChangeListener(this);
780                }
781            }
782            // and add them back in
783            for (int i = 0; i < _numConditionalNGs; i++) {
784                c = _curLogixNG.getConditionalNG(i);
785                if (c != null) {
786                    c.addPropertyChangeListener(this);
787                }
788            }
789        }
790
791        public void setShowStartupThreads(boolean showStartupThreads) {
792            _showStartupThreads = showStartupThreads;
793            fireTableRowsUpdated(0, _curLogixNG.getNumConditionalNGs()-1);
794        }
795
796        @Override
797        public void propertyChange(java.beans.PropertyChangeEvent e) {
798            if (e.getPropertyName().equals("length")) {  // NOI18N
799                // a new NamedBean is available in the manager
800                updateConditionalNGListeners();
801                fireTableDataChanged();
802            } else if (matchPropertyName(e)) {
803                // a value changed.
804                fireTableDataChanged();
805            }
806        }
807
808        /**
809         * Check if this property event is announcing a change this table should
810         * display.
811         * <p>
812         * Note that events will come both from the NamedBeans and from the
813         * manager.
814         *
815         * @param e the event heard
816         * @return true if a change in State or Appearance was heard
817         */
818        boolean matchPropertyName(java.beans.PropertyChangeEvent e) {
819            return (e.getPropertyName().contains("UserName") ||      // NOI18N
820                    e.getPropertyName().contains("Thread"));  // NOI18N
821        }
822
823        @Override
824        public Class<?> getColumnClass(int c) {
825            if ((c == BUTTON_COLUMN)
826                    || (c == BUTTON_DEBUG_COLUMN)
827                    || (c == BUTTON_DELETE_COLUMN)
828                    || (c == BUTTON_EDIT_THREADS_COLUMN)) {
829                return JButton.class;
830            }
831            if (c == STARTUP_COLUMN
832                    || c == ENABLED_COLUMN) {
833                return Boolean.class;
834            }
835            return String.class;
836        }
837
838        @Override
839        public int getColumnCount() {
840            return NUM_COLUMNS;
841        }
842
843        @Override
844        public int getRowCount() {
845            return (_numConditionalNGs);
846        }
847
848        @Override
849        public boolean isCellEditable(int r, int c) {
850            if (!_inReorderMode) {
851                return ((c == UNAME_COLUMN)
852                        || (c == ENABLED_COLUMN)
853                        || (c == STARTUP_COLUMN)
854                        || (c == BUTTON_COLUMN)
855                        || ((c == BUTTON_DEBUG_COLUMN) && InstanceManager.getDefault(LogixNGPreferences.class).getInstallDebugger())
856                        || (c == BUTTON_DELETE_COLUMN)
857                        || (c == BUTTON_EDIT_THREADS_COLUMN));
858            } else if (c == BUTTON_COLUMN) {
859                if (r >= _nextInOrder) {
860                    return (true);
861                }
862            }
863            return (false);
864        }
865
866        @Override
867        public String getColumnName(int col) {
868            switch (col) {
869                case SNAME_COLUMN:
870                    return Bundle.getMessage("ColumnSystemName");  // NOI18N
871                case UNAME_COLUMN:
872                    return Bundle.getMessage("ColumnUserName");  // NOI18N
873                case ENABLED_COLUMN:
874                    return Bundle.getMessage("ColumnHeadEnabled");  // NOI18N
875                case THREAD_COLUMN:
876                    return Bundle.getMessage("ConditionalNG_Table_ColumnThreadName");  // NOI18N
877                case STARTUP_COLUMN:
878                    return Bundle.getMessage("ConditionalNG_Table_ColumnStartup");  // NOI18N
879                case BUTTON_COLUMN:
880                    return ""; // no label
881                case BUTTON_DEBUG_COLUMN:
882                    return ""; // no label
883                case BUTTON_DELETE_COLUMN:
884                    return ""; // no label
885                case BUTTON_EDIT_THREADS_COLUMN:
886                    return ""; // no label
887                default:
888                    throw new IllegalArgumentException("Unknown column");
889            }
890        }
891
892        @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES",
893                justification = "better to keep cases in column order rather than to combine")
894        public int getPreferredWidth(int col) {
895            switch (col) {
896                case SNAME_COLUMN:
897                    return new JTextField(6).getPreferredSize().width;
898                case UNAME_COLUMN:
899                    return new JTextField(17).getPreferredSize().width;
900                case ENABLED_COLUMN:
901                    return new JTextField(5).getPreferredSize().width;
902                case THREAD_COLUMN:
903                    return new JTextField(10).getPreferredSize().width;
904                case STARTUP_COLUMN:
905                    return new JTextField(6).getPreferredSize().width;
906                case BUTTON_COLUMN:
907                    return new JTextField(6).getPreferredSize().width;
908                case BUTTON_DEBUG_COLUMN:
909                    return new JTextField(6).getPreferredSize().width;
910                case BUTTON_DELETE_COLUMN:
911                    return new JTextField(6).getPreferredSize().width;
912                case BUTTON_EDIT_THREADS_COLUMN:
913                    return new JTextField(12).getPreferredSize().width;
914                default:
915                    throw new IllegalArgumentException("Unknown column");
916            }
917        }
918
919        @Override
920        public Object getValueAt(int r, int col) {
921            ConditionalNG c;
922            int rx = r;
923            if ((rx > _numConditionalNGs) || (_curLogixNG == null)) {
924                return null;
925            }
926            switch (col) {
927                case BUTTON_COLUMN:
928                    if (!_inReorderMode) {
929                        return Bundle.getMessage("ButtonEdit");  // NOI18N
930                    } else if (_nextInOrder == 0) {
931                        return Bundle.getMessage("ButtonFirst");  // NOI18N
932                    } else if (_nextInOrder <= r) {
933                        return Bundle.getMessage("ButtonNext");  // NOI18N
934                    } else {
935                        return Integer.toString(rx + 1);
936                    }
937                case BUTTON_DEBUG_COLUMN:
938                    return Bundle.getMessage("ConditionalNG_Table_ButtonDebug");  // NOI18N
939                case BUTTON_DELETE_COLUMN:
940                    return Bundle.getMessage("ButtonDelete");  // NOI18N
941                case BUTTON_EDIT_THREADS_COLUMN:
942                    return Bundle.getMessage("ConditionalNG_Table_ButtonEditThreads");  // NOI18N
943                case SNAME_COLUMN:
944                    return _curLogixNG.getConditionalNG(rx);
945                case UNAME_COLUMN:
946                    //log.debug("ConditionalNGTableModel: {}", _curLogixNG.getConditionalNGByNumberOrder(rx));  // NOI18N
947                    c = _curLogixNG.getConditionalNG(rx);
948                    if (c != null) {
949                        return c.getUserName();
950                    }
951                    return "";
952                case ENABLED_COLUMN:
953                    c = _curLogixNG.getConditionalNG(rx);
954                    if (c != null) {
955                        return c.isEnabled();
956                    }
957                    return null;
958                case THREAD_COLUMN:
959                    if (_showStartupThreads) {
960                        return LogixNG_Thread.getThread(
961                                _curLogixNG.getConditionalNG(r).getStartupThreadId())
962                                .getThreadName();
963                    } else {
964                        return _curLogixNG.getConditionalNG(r).getCurrentThread().getThreadName();
965                    }
966                case STARTUP_COLUMN:
967                    c = _curLogixNG.getConditionalNG(rx);
968                    if (c != null) {
969                        return c.isExecuteAtStartup();
970                    }
971                    return false;
972                default:
973                    throw new IllegalArgumentException("Unknown column");
974            }
975        }
976
977        private void buttonStartupClicked(int row, Object value) {
978            _curConditionalNG = _curLogixNG.getConditionalNG(row);
979            if (_curConditionalNG == null) {
980                log.error("Attempted edit of non-existant conditional.");  // NOI18N
981                return;
982            }
983            if (!(value instanceof Boolean)) {
984                throw new IllegalArgumentException("value is not a Boolean");
985            }
986            _curConditionalNG.setExecuteAtStartup((boolean)value);
987        }
988
989        private void buttonColumnClicked(int row, int col) {
990            if (_inReorderMode) {
991                swapConditionalNG(row);
992            } else {
993                // Use separate Runnable so window is created on top
994                class WindowMaker implements Runnable {
995
996                    int theRow;
997
998                    WindowMaker(int r) {
999                        theRow = r;
1000                    }
1001
1002                    @Override
1003                    public void run() {
1004                        editConditionalNGPressed(theRow);
1005                    }
1006                }
1007                WindowMaker t = new WindowMaker(row);
1008                javax.swing.SwingUtilities.invokeLater(t);
1009            }
1010        }
1011
1012        private void buttonDebugClicked(int row, int col) {
1013            if (_inReorderMode) {
1014                swapConditionalNG(row);
1015            } else {
1016                // Use separate Runnable so window is created on top
1017                class WindowMaker implements Runnable {
1018
1019                    int theRow;
1020
1021                    WindowMaker(int r) {
1022                        theRow = r;
1023                    }
1024
1025                    @Override
1026                    public void run() {
1027                        debugConditionalNGPressed(theRow);
1028                    }
1029                }
1030                WindowMaker t = new WindowMaker(row);
1031                javax.swing.SwingUtilities.invokeLater(t);
1032            }
1033        }
1034
1035        private void deleteConditionalNG(int row) {
1036            DeleteBeanWorker worker = new DeleteBeanWorker(_curLogixNG.getConditionalNG(row), row);
1037            worker.execute();
1038        }
1039
1040        private void changeUserName(Object value, int row) {
1041            String uName = (String) value;
1042            ConditionalNG cn = _curLogixNG.getConditionalNGByUserName(uName);
1043            if (cn == null) {
1044                ConditionalNG cdl = _curLogixNG.getConditionalNG(row);
1045                cdl.setUserName(uName.trim()); // N11N
1046                fireTableRowsUpdated(row, row);
1047            } else {
1048                // Duplicate user name
1049                if (cn != _curLogixNG.getConditionalNG(row)) {
1050                    messageDuplicateConditionalNGUserName(cn.getSystemName());
1051                }
1052            }
1053        }
1054
1055        @Override
1056        public void setValueAt(Object value, int row, int col) {
1057            if ((row > _numConditionalNGs) || (_curLogixNG == null)) {
1058                return;
1059            }
1060            switch (col) {
1061                case STARTUP_COLUMN:
1062                    buttonStartupClicked(row, value);
1063                    break;
1064                case BUTTON_COLUMN:
1065                    buttonColumnClicked(row, col);
1066                    break;
1067                case BUTTON_DEBUG_COLUMN:
1068                    buttonDebugClicked(row, col);
1069                    break;
1070                case BUTTON_DELETE_COLUMN:
1071                    deleteConditionalNG(row);
1072                    break;
1073                case BUTTON_EDIT_THREADS_COLUMN:
1074                    EditThreadsDialog dialog = new EditThreadsDialog(_curLogixNG.getConditionalNG(row));
1075                    dialog.showDialog();
1076                    break;
1077                case SNAME_COLUMN:
1078                    throw new IllegalArgumentException("System name cannot be changed");
1079                case UNAME_COLUMN:
1080                    changeUserName(value, row);
1081                    break;
1082                case ENABLED_COLUMN:
1083                    ConditionalNG c = _curLogixNG.getConditionalNG(row);
1084                    if (c != null) {
1085                        boolean v = c.isEnabled();
1086                        c.setEnabled(!v);
1087                    }
1088                    break;
1089                default:
1090                    throw new IllegalArgumentException("Unknown column");
1091            }
1092        }
1093    }
1094
1095    /**
1096     * Send a duplicate Conditional user name message for Edit Logix pane.
1097     *
1098     * @param svName proposed name that duplicates an existing name
1099     */
1100    void messageDuplicateConditionalNGUserName(String svName) {
1101        JmriJOptionPane.showMessageDialog(null,
1102                Bundle.getMessage("Error30", svName),
1103                Bundle.getMessage("ErrorTitle"), // NOI18N
1104                JmriJOptionPane.ERROR_MESSAGE);
1105    }
1106
1107    private String getClassName() {
1108        // The class that is returned must have a default constructor,
1109        // a constructor with no parameters.
1110        return jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName();
1111    }
1112
1113
1114    // ------------ LogixNG Notifications ------------
1115    // The ConditionalNG views support some direct changes to the parent logix.
1116    // This custom event is used to notify the parent LogixNG that changes are requested.
1117    // When the event occurs, the parent LogixNG can retrieve the necessary information
1118    // to carry out the actions.
1119    //
1120    // 1) Notify the calling LogixNG that the LogixNG user name has been changed.
1121    // 2) Notify the calling LogixNG that the conditional view is closing
1122    // 3) Notify the calling LogixNG that it is to be deleted
1123    /**
1124     * Create a custom listener event.
1125     */
1126    public interface LogixNGEventListener extends EventListener {
1127
1128        void logixNGEventOccurred();
1129    }
1130
1131    /**
1132     * Maintain a list of listeners -- normally only one.
1133     */
1134    List<EditorEventListener> listenerList = new ArrayList<>();
1135
1136    /**
1137     * This contains a list of commands to be processed by the listener
1138     * recipient.
1139     */
1140    private HashMap<String, String> logixNG_Data = new HashMap<>();
1141
1142    /**
1143     * Add a listener.
1144     *
1145     * @param listener The recipient
1146     */
1147    @Override
1148    public void addEditorEventListener(EditorEventListener listener) {
1149        listenerList.add(listener);
1150    }
1151
1152    /**
1153     * Remove a listener -- not used.
1154     *
1155     * @param listener The recipient
1156     */
1157    @Override
1158    public void removeEditorEventListener(EditorEventListener listener) {
1159        listenerList.remove(listener);
1160    }
1161
1162    /**
1163     * Notify the listeners to check for new data.
1164     */
1165    private void fireEditorEvent() {
1166        for (EditorEventListener l : listenerList) {
1167            l.editorEventOccurred(logixNG_Data);
1168        }
1169    }
1170
1171
1172    private class LogixNGEventListenerImpl implements ConditionalNGEditor.ConditionalNGEventListener {
1173
1174        private final LogixNGEditor _logixNGEditor;
1175
1176        public LogixNGEventListenerImpl(LogixNGEditor logixNGEditor) {
1177            this._logixNGEditor = logixNGEditor;
1178        }
1179
1180        @Override
1181        public void conditionalNGEventOccurred() {
1182            String lgxName = _curLogixNG.getSystemName();
1183            _treeEdit.logixNGData.forEach((key, value) -> {
1184                if (key.equals("Finish")) {                  // NOI18N
1185                    _treeEdit = null;
1186                    _inEditConditionalNGMode = false;
1187                    _logixNGEditor.bringToFront();
1188                } else if (key.equals("Delete")) {           // NOI18N
1189                    deletePressed();
1190                } else if (key.equals("chgUname")) {         // NOI18N
1191                    LogixNG x = _logixNG_Manager.getBySystemName(lgxName);
1192                    if (x == null) {
1193                        log.error("Found no logixNG for name {} when changing user name (2)", lgxName);
1194                        return;
1195                    }
1196                    x.setUserName(value);
1197
1198                    if (beanTableDataModel != null) {
1199                        beanTableDataModel.fireTableDataChanged();
1200                    }
1201                }
1202            });
1203        }
1204    }
1205
1206
1207    private class LogixNG_DebuggerEventListenerImpl
1208            implements ConditionalNGDebugger.ConditionalNGEventListener {
1209
1210        private final LogixNGEditor _logixNGEditor;
1211
1212        public LogixNG_DebuggerEventListenerImpl(LogixNGEditor logixNGEditor) {
1213            this._logixNGEditor = logixNGEditor;
1214        }
1215
1216        @Override
1217        public void conditionalNGEventOccurred() {
1218            String lgxName = _curLogixNG.getSystemName();
1219            _debugger.logixNGData.forEach((key, value) -> {
1220                if (key.equals("Finish")) {                  // NOI18N
1221                    _debugger = null;
1222                    _inEditConditionalNGMode = false;
1223                    _logixNGEditor.bringToFront();
1224                } else if (key.equals("Delete")) {           // NOI18N
1225                    deletePressed();
1226                } else if (key.equals("chgUname")) {         // NOI18N
1227                    LogixNG x = _logixNG_Manager.getBySystemName(lgxName);
1228                    if (x == null) {
1229                        log.error("Found no logixNG for name {} when changing user name (2)", lgxName);
1230                        return;
1231                    }
1232                    x.setUserName(value);
1233
1234                    if (beanTableDataModel != null) {
1235                        beanTableDataModel.fireTableDataChanged();
1236                    }
1237                }
1238            });
1239        }
1240    }
1241
1242
1243    // This class is copied from BeanTableDataModel
1244    private class DeleteBeanWorker extends SwingWorker<Void, Void> {
1245
1246        private final ConditionalNG _conditionalNG;
1247        private final int _row;
1248        boolean _hasDeleted = false;
1249
1250        public DeleteBeanWorker(ConditionalNG conditionalNG, int row) {
1251            _conditionalNG = conditionalNG;
1252            _row = row;
1253        }
1254
1255        public int getDisplayDeleteMsg() {
1256            return InstanceManager.getDefault(UserPreferencesManager.class).getMultipleChoiceOption(TreeEditor.class.getName(), "deleteInUse");
1257        }
1258
1259        public void setDisplayDeleteMsg(int boo) {
1260            InstanceManager.getDefault(UserPreferencesManager.class).setMultipleChoiceOption(TreeEditor.class.getName(), "deleteInUse", boo);
1261        }
1262
1263        public void doDelete() {
1264            try {
1265                InstanceManager.getDefault(ConditionalNG_Manager.class).deleteBean(_conditionalNG, "DoDelete");  // NOI18N
1266                _conditionalNGTableModel.fireTableRowsDeleted(_row, _row);
1267                _numConditionalNGs--;
1268                _showReminder = true;
1269                _hasDeleted = true;
1270            } catch (PropertyVetoException e) {
1271                //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto
1272                log.error("Unexpected doDelete failure for {}, {}", _conditionalNG, e.getMessage() );
1273            }
1274        }
1275
1276        /**
1277         * {@inheritDoc}
1278         */
1279        @Override
1280        public Void doInBackground() {
1281            _conditionalNG.getFemaleSocket().unregisterListeners();
1282
1283            StringBuilder message = new StringBuilder();
1284            try {
1285                InstanceManager.getDefault(ConditionalNG_Manager.class).deleteBean(_conditionalNG, "CanDelete");  // NOI18N
1286            } catch (PropertyVetoException e) {
1287                if (e.getPropertyChangeEvent().getPropertyName().equals("DoNotDelete")) { // NOI18N
1288                    log.warn("Do not Delete {}, {}", _conditionalNG, e.getMessage());
1289                    message.append(Bundle.getMessage("VetoDeleteBean", _conditionalNG.getBeanType(), _conditionalNG.getDisplayName(NamedBean.DisplayOptions.USERNAME_SYSTEMNAME), e.getMessage()));
1290                    JmriJOptionPane.showMessageDialog(null, message.toString(),
1291                            Bundle.getMessage("WarningTitle"),
1292                            JmriJOptionPane.ERROR_MESSAGE);
1293                    return null;
1294                }
1295                message.append(e.getMessage());
1296            }
1297            List<String> listenerRefs = new ArrayList<>();
1298            _conditionalNG.getListenerRefsIncludingChildren(listenerRefs);
1299            int listenerRefsCount = listenerRefs.size();
1300            log.debug("Delete with {}", listenerRefsCount);
1301            if (getDisplayDeleteMsg() == 0x02 && message.toString().isEmpty()) {
1302                doDelete();
1303            } else {
1304                final JDialog dialog = new JDialog();
1305                dialog.setTitle(Bundle.getMessage("WarningTitle"));
1306                dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1307                JPanel container = new JPanel();
1308                container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
1309                container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
1310
1311                if (listenerRefsCount > 0) { // warn of listeners attached before delete
1312                    String prompt = _conditionalNG.getFemaleSocket().isConnected()
1313                            ? "DeleteWithChildrenPrompt" : "DeletePrompt";
1314                    JLabel question = new JLabel(Bundle.getMessage(prompt, _conditionalNG.getDisplayName(NamedBean.DisplayOptions.USERNAME_SYSTEMNAME)));
1315                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
1316                    container.add(question);
1317
1318                    ArrayList<String> listeners = new ArrayList<>();
1319                    for (String listenerRef : listenerRefs) {
1320                        if (!listeners.contains(listenerRef)) {
1321                            listeners.add(listenerRef);
1322                        }
1323                    }
1324
1325                    message.append("<br>");
1326                    message.append(Bundle.getMessage("ReminderInUse", listenerRefsCount));
1327                    message.append("<ul>");
1328                    for (String listener : listeners) {
1329                        message.append("<li>");
1330                        message.append(listener);
1331                        message.append("</li>");
1332                    }
1333                    message.append("</ul>");
1334
1335                    JEditorPane pane = new JEditorPane();
1336                    pane.setContentType("text/html");
1337                    pane.setText("<html>" + message.toString() + "</html>");
1338                    pane.setEditable(false);
1339                    JScrollPane jScrollPane = new JScrollPane(pane);
1340                    container.add(jScrollPane);
1341                } else {
1342                    String prompt = _conditionalNG.getFemaleSocket().isConnected()
1343                            ? "DeleteWithChildrenPrompt" : "DeletePrompt";
1344                    String msg = MessageFormat.format(
1345                            Bundle.getMessage(prompt), _conditionalNG.getSystemName());
1346                    JLabel question = new JLabel(msg);
1347                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
1348                    container.add(question);
1349                }
1350
1351                final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
1352                remember.setFont(remember.getFont().deriveFont(10f));
1353                remember.setAlignmentX(Component.CENTER_ALIGNMENT);
1354
1355                JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
1356                JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
1357                JPanel button = new JPanel();
1358                button.setAlignmentX(Component.CENTER_ALIGNMENT);
1359                button.add(yesButton);
1360                button.add(noButton);
1361                container.add(button);
1362
1363                noButton.addActionListener((ActionEvent e) -> {
1364                    //there is no point in remembering this the user will never be
1365                    //able to delete a bean!
1366                    dialog.dispose();
1367                });
1368
1369                yesButton.addActionListener((ActionEvent e) -> {
1370                    if (remember.isSelected()) {
1371                        setDisplayDeleteMsg(0x02);
1372                    }
1373                    doDelete();
1374                    dialog.dispose();
1375                });
1376                container.add(remember);
1377                container.setAlignmentX(Component.CENTER_ALIGNMENT);
1378                container.setAlignmentY(Component.CENTER_ALIGNMENT);
1379                dialog.getContentPane().add(container);
1380                dialog.pack();
1381
1382                dialog.getRootPane().setDefaultButton(noButton);
1383                noButton.requestFocusInWindow(); // set default keyboard focus, after pack() before setVisible(true)
1384                dialog.getRootPane().registerKeyboardAction(e -> { // escape to exit
1385                        dialog.setVisible(false);
1386                        dialog.dispose(); },
1387                    KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
1388
1389                dialog.setLocation((Toolkit.getDefaultToolkit().getScreenSize().width) / 2 - dialog.getWidth() / 2, (Toolkit.getDefaultToolkit().getScreenSize().height) / 2 - dialog.getHeight() / 2);
1390                dialog.setModal(true);
1391                dialog.setVisible(true);
1392            }
1393            if (!_hasDeleted && _conditionalNG.getFemaleSocket().isActive()) _conditionalNG.getFemaleSocket().registerListeners();
1394            return null;
1395        }
1396
1397        /**
1398         * {@inheritDoc} Minimal implementation to catch and log errors
1399         */
1400        @Override
1401        protected void done() {
1402            try {
1403                get();  // called to get errors
1404            } catch (InterruptedException | java.util.concurrent.ExecutionException e) {
1405                log.error("Exception while deleting bean", e);
1406            }
1407        }
1408    }
1409
1410
1411    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNGEditor.class);
1412
1413}