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 STARTUP_COLUMN = THREAD_COLUMN + 1;
756        public static final int BUTTON_COLUMN = STARTUP_COLUMN + 1;
757        public static final int BUTTON_DEBUG_COLUMN = BUTTON_COLUMN + 1;
758        public static final int BUTTON_DELETE_COLUMN = BUTTON_DEBUG_COLUMN + 1;
759        public static final int BUTTON_EDIT_THREADS_COLUMN = BUTTON_DELETE_COLUMN + 1;
760        public static final int NUM_COLUMNS = BUTTON_EDIT_THREADS_COLUMN + 1;
761
762        private boolean _showStartupThreads;
763
764
765        public ConditionalNGTableModel() {
766            super();
767            updateConditionalNGListeners();
768        }
769
770        synchronized void updateConditionalNGListeners() {
771            // first, remove listeners from the individual objects
772            ConditionalNG c;
773            _numConditionalNGs = _curLogixNG.getNumConditionalNGs();
774            for (int i = 0; i < _numConditionalNGs; i++) {
775                // if object has been deleted, it's not here; ignore it
776                c = _curLogixNG.getConditionalNG(i);
777                if (c != null) {
778                    c.removePropertyChangeListener(this);
779                }
780            }
781            // and add them back in
782            for (int i = 0; i < _numConditionalNGs; i++) {
783                c = _curLogixNG.getConditionalNG(i);
784                if (c != null) {
785                    c.addPropertyChangeListener(this);
786                }
787            }
788        }
789
790        public void setShowStartupThreads(boolean showStartupThreads) {
791            _showStartupThreads = showStartupThreads;
792            fireTableRowsUpdated(0, _curLogixNG.getNumConditionalNGs()-1);
793        }
794
795        @Override
796        public void propertyChange(java.beans.PropertyChangeEvent e) {
797            if (e.getPropertyName().equals("length")) {  // NOI18N
798                // a new NamedBean is available in the manager
799                updateConditionalNGListeners();
800                fireTableDataChanged();
801            } else if (matchPropertyName(e)) {
802                // a value changed.
803                fireTableDataChanged();
804            }
805        }
806
807        /**
808         * Check if this property event is announcing a change this table should
809         * display.
810         * <p>
811         * Note that events will come both from the NamedBeans and from the
812         * manager.
813         *
814         * @param e the event heard
815         * @return true if a change in State or Appearance was heard
816         */
817        boolean matchPropertyName(java.beans.PropertyChangeEvent e) {
818            return (e.getPropertyName().contains("UserName") ||      // NOI18N
819                    e.getPropertyName().contains("Thread"));  // NOI18N
820        }
821
822        @Override
823        public Class<?> getColumnClass(int c) {
824            if ((c == BUTTON_COLUMN)
825                    || (c == BUTTON_DEBUG_COLUMN)
826                    || (c == BUTTON_DELETE_COLUMN)
827                    || (c == BUTTON_EDIT_THREADS_COLUMN)) {
828                return JButton.class;
829            }
830            if (c == STARTUP_COLUMN) {
831                return Boolean.class;
832            }
833            return String.class;
834        }
835
836        @Override
837        public int getColumnCount() {
838            return NUM_COLUMNS;
839        }
840
841        @Override
842        public int getRowCount() {
843            return (_numConditionalNGs);
844        }
845
846        @Override
847        public boolean isCellEditable(int r, int c) {
848            if (!_inReorderMode) {
849                return ((c == UNAME_COLUMN)
850                        || (c == STARTUP_COLUMN)
851                        || (c == BUTTON_COLUMN)
852                        || ((c == BUTTON_DEBUG_COLUMN) && InstanceManager.getDefault(LogixNGPreferences.class).getInstallDebugger())
853                        || (c == BUTTON_DELETE_COLUMN)
854                        || (c == BUTTON_EDIT_THREADS_COLUMN));
855            } else if (c == BUTTON_COLUMN) {
856                if (r >= _nextInOrder) {
857                    return (true);
858                }
859            }
860            return (false);
861        }
862
863        @Override
864        public String getColumnName(int col) {
865            switch (col) {
866                case SNAME_COLUMN:
867                    return Bundle.getMessage("ColumnSystemName");  // NOI18N
868                case UNAME_COLUMN:
869                    return Bundle.getMessage("ColumnUserName");  // NOI18N
870                case THREAD_COLUMN:
871                    return Bundle.getMessage("ConditionalNG_Table_ColumnThreadName");  // NOI18N
872                case STARTUP_COLUMN:
873                    return Bundle.getMessage("ConditionalNG_Table_ColumnStartup");  // NOI18N
874                case BUTTON_COLUMN:
875                    return ""; // no label
876                case BUTTON_DEBUG_COLUMN:
877                    return ""; // no label
878                case BUTTON_DELETE_COLUMN:
879                    return ""; // no label
880                case BUTTON_EDIT_THREADS_COLUMN:
881                    return ""; // no label
882                default:
883                    throw new IllegalArgumentException("Unknown column");
884            }
885        }
886
887        @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES",
888                justification = "better to keep cases in column order rather than to combine")
889        public int getPreferredWidth(int col) {
890            switch (col) {
891                case SNAME_COLUMN:
892                    return new JTextField(6).getPreferredSize().width;
893                case UNAME_COLUMN:
894                    return new JTextField(17).getPreferredSize().width;
895                case THREAD_COLUMN:
896                    return new JTextField(10).getPreferredSize().width;
897                case STARTUP_COLUMN:
898                    return new JTextField(6).getPreferredSize().width;
899                case BUTTON_COLUMN:
900                    return new JTextField(6).getPreferredSize().width;
901                case BUTTON_DEBUG_COLUMN:
902                    return new JTextField(6).getPreferredSize().width;
903                case BUTTON_DELETE_COLUMN:
904                    return new JTextField(6).getPreferredSize().width;
905                case BUTTON_EDIT_THREADS_COLUMN:
906                    return new JTextField(12).getPreferredSize().width;
907                default:
908                    throw new IllegalArgumentException("Unknown column");
909            }
910        }
911
912        @Override
913        public Object getValueAt(int r, int col) {
914            int rx = r;
915            if ((rx > _numConditionalNGs) || (_curLogixNG == null)) {
916                return null;
917            }
918            switch (col) {
919                case BUTTON_COLUMN:
920                    if (!_inReorderMode) {
921                        return Bundle.getMessage("ButtonEdit");  // NOI18N
922                    } else if (_nextInOrder == 0) {
923                        return Bundle.getMessage("ButtonFirst");  // NOI18N
924                    } else if (_nextInOrder <= r) {
925                        return Bundle.getMessage("ButtonNext");  // NOI18N
926                    } else {
927                        return Integer.toString(rx + 1);
928                    }
929                case BUTTON_DEBUG_COLUMN:
930                    return Bundle.getMessage("ConditionalNG_Table_ButtonDebug");  // NOI18N
931                case BUTTON_DELETE_COLUMN:
932                    return Bundle.getMessage("ButtonDelete");  // NOI18N
933                case BUTTON_EDIT_THREADS_COLUMN:
934                    return Bundle.getMessage("ConditionalNG_Table_ButtonEditThreads");  // NOI18N
935                case SNAME_COLUMN:
936                    return _curLogixNG.getConditionalNG(rx);
937                case UNAME_COLUMN: {
938                    //log.debug("ConditionalNGTableModel: {}", _curLogixNG.getConditionalNGByNumberOrder(rx));  // NOI18N
939                    ConditionalNG c = _curLogixNG.getConditionalNG(rx);
940                    if (c != null) {
941                        return c.getUserName();
942                    }
943                    return "";
944                }
945                case THREAD_COLUMN:
946                    if (_showStartupThreads) {
947                        return LogixNG_Thread.getThread(
948                                _curLogixNG.getConditionalNG(r).getStartupThreadId())
949                                .getThreadName();
950                    } else {
951                        return _curLogixNG.getConditionalNG(r).getCurrentThread().getThreadName();
952                    }
953                case STARTUP_COLUMN:
954                    ConditionalNG c = _curLogixNG.getConditionalNG(rx);
955                    if (c != null) {
956                        return c.isExecuteAtStartup();
957                    }
958                    return false;
959                default:
960                    throw new IllegalArgumentException("Unknown column");
961            }
962        }
963
964        private void buttonStartupClicked(int row, Object value) {
965            _curConditionalNG = _curLogixNG.getConditionalNG(row);
966            if (_curConditionalNG == null) {
967                log.error("Attempted edit of non-existant conditional.");  // NOI18N
968                return;
969            }
970            if (!(value instanceof Boolean)) {
971                throw new IllegalArgumentException("value is not a Boolean");
972            }
973            _curConditionalNG.setExecuteAtStartup((boolean)value);
974        }
975
976        private void buttonColumnClicked(int row, int col) {
977            if (_inReorderMode) {
978                swapConditionalNG(row);
979            } else {
980                // Use separate Runnable so window is created on top
981                class WindowMaker implements Runnable {
982
983                    int row;
984
985                    WindowMaker(int r) {
986                        row = r;
987                    }
988
989                    @Override
990                    public void run() {
991                        editConditionalNGPressed(row);
992                    }
993                }
994                WindowMaker t = new WindowMaker(row);
995                javax.swing.SwingUtilities.invokeLater(t);
996            }
997        }
998
999        private void buttonDebugClicked(int row, int col) {
1000            if (_inReorderMode) {
1001                swapConditionalNG(row);
1002            } else {
1003                // Use separate Runnable so window is created on top
1004                class WindowMaker implements Runnable {
1005
1006                    int row;
1007
1008                    WindowMaker(int r) {
1009                        row = r;
1010                    }
1011
1012                    @Override
1013                    public void run() {
1014                        debugConditionalNGPressed(row);
1015                    }
1016                }
1017                WindowMaker t = new WindowMaker(row);
1018                javax.swing.SwingUtilities.invokeLater(t);
1019            }
1020        }
1021
1022        private void deleteConditionalNG(int row) {
1023            DeleteBeanWorker worker = new DeleteBeanWorker(_curLogixNG.getConditionalNG(row), row);
1024            worker.execute();
1025        }
1026
1027        private void changeUserName(Object value, int row) {
1028            String uName = (String) value;
1029            ConditionalNG cn = _curLogixNG.getConditionalNGByUserName(uName);
1030            if (cn == null) {
1031                ConditionalNG cdl = _curLogixNG.getConditionalNG(row);
1032                cdl.setUserName(uName.trim()); // N11N
1033                fireTableRowsUpdated(row, row);
1034            } else {
1035                // Duplicate user name
1036                if (cn != _curLogixNG.getConditionalNG(row)) {
1037                    messageDuplicateConditionalNGUserName(cn.getSystemName());
1038                }
1039            }
1040        }
1041
1042        @Override
1043        public void setValueAt(Object value, int row, int col) {
1044            if ((row > _numConditionalNGs) || (_curLogixNG == null)) {
1045                return;
1046            }
1047            switch (col) {
1048                case STARTUP_COLUMN:
1049                    buttonStartupClicked(row, value);
1050                    break;
1051                case BUTTON_COLUMN:
1052                    buttonColumnClicked(row, col);
1053                    break;
1054                case BUTTON_DEBUG_COLUMN:
1055                    buttonDebugClicked(row, col);
1056                    break;
1057                case BUTTON_DELETE_COLUMN:
1058                    deleteConditionalNG(row);
1059                    break;
1060                case BUTTON_EDIT_THREADS_COLUMN:
1061                    EditThreadsDialog dialog = new EditThreadsDialog(_curLogixNG.getConditionalNG(row));
1062                    dialog.showDialog();
1063                    break;
1064                case SNAME_COLUMN:
1065                    throw new IllegalArgumentException("System name cannot be changed");
1066                case UNAME_COLUMN: {
1067                    changeUserName(value, row);
1068                    break;
1069                }
1070                default:
1071                    throw new IllegalArgumentException("Unknown column");
1072            }
1073        }
1074    }
1075
1076    /**
1077     * Send a duplicate Conditional user name message for Edit Logix pane.
1078     *
1079     * @param svName proposed name that duplicates an existing name
1080     */
1081    void messageDuplicateConditionalNGUserName(String svName) {
1082        JmriJOptionPane.showMessageDialog(null,
1083                Bundle.getMessage("Error30", svName),
1084                Bundle.getMessage("ErrorTitle"), // NOI18N
1085                JmriJOptionPane.ERROR_MESSAGE);
1086    }
1087
1088    private String getClassName() {
1089        // The class that is returned must have a default constructor,
1090        // a constructor with no parameters.
1091        return jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName();
1092    }
1093
1094
1095    // ------------ LogixNG Notifications ------------
1096    // The ConditionalNG views support some direct changes to the parent logix.
1097    // This custom event is used to notify the parent LogixNG that changes are requested.
1098    // When the event occurs, the parent LogixNG can retrieve the necessary information
1099    // to carry out the actions.
1100    //
1101    // 1) Notify the calling LogixNG that the LogixNG user name has been changed.
1102    // 2) Notify the calling LogixNG that the conditional view is closing
1103    // 3) Notify the calling LogixNG that it is to be deleted
1104    /**
1105     * Create a custom listener event.
1106     */
1107    public interface LogixNGEventListener extends EventListener {
1108
1109        void logixNGEventOccurred();
1110    }
1111
1112    /**
1113     * Maintain a list of listeners -- normally only one.
1114     */
1115    List<EditorEventListener> listenerList = new ArrayList<>();
1116
1117    /**
1118     * This contains a list of commands to be processed by the listener
1119     * recipient.
1120     */
1121    private HashMap<String, String> logixNG_Data = new HashMap<>();
1122
1123    /**
1124     * Add a listener.
1125     *
1126     * @param listener The recipient
1127     */
1128    @Override
1129    public void addEditorEventListener(EditorEventListener listener) {
1130        listenerList.add(listener);
1131    }
1132
1133    /**
1134     * Remove a listener -- not used.
1135     *
1136     * @param listener The recipient
1137     */
1138    @Override
1139    public void removeEditorEventListener(EditorEventListener listener) {
1140        listenerList.remove(listener);
1141    }
1142
1143    /**
1144     * Notify the listeners to check for new data.
1145     */
1146    private void fireEditorEvent() {
1147        for (EditorEventListener l : listenerList) {
1148            l.editorEventOccurred(logixNG_Data);
1149        }
1150    }
1151
1152
1153    private class LogixNGEventListenerImpl implements ConditionalNGEditor.ConditionalNGEventListener {
1154
1155        private final LogixNGEditor _logixNGEditor;
1156
1157        public LogixNGEventListenerImpl(LogixNGEditor logixNGEditor) {
1158            this._logixNGEditor = logixNGEditor;
1159        }
1160
1161        @Override
1162        public void conditionalNGEventOccurred() {
1163            String lgxName = _curLogixNG.getSystemName();
1164            _treeEdit.logixNGData.forEach((key, value) -> {
1165                if (key.equals("Finish")) {                  // NOI18N
1166                    _treeEdit = null;
1167                    _inEditConditionalNGMode = false;
1168                    _logixNGEditor.bringToFront();
1169                } else if (key.equals("Delete")) {           // NOI18N
1170                    deletePressed();
1171                } else if (key.equals("chgUname")) {         // NOI18N
1172                    LogixNG x = _logixNG_Manager.getBySystemName(lgxName);
1173                    if (x == null) {
1174                        log.error("Found no logixNG for name {} when changing user name (2)", lgxName);
1175                        return;
1176                    }
1177                    x.setUserName(value);
1178
1179                    if (beanTableDataModel != null) {
1180                        beanTableDataModel.fireTableDataChanged();
1181                    }
1182                }
1183            });
1184        }
1185    }
1186
1187
1188    private class LogixNG_DebuggerEventListenerImpl
1189            implements ConditionalNGDebugger.ConditionalNGEventListener {
1190
1191        private final LogixNGEditor _logixNGEditor;
1192
1193        public LogixNG_DebuggerEventListenerImpl(LogixNGEditor logixNGEditor) {
1194            this._logixNGEditor = logixNGEditor;
1195        }
1196
1197        @Override
1198        public void conditionalNGEventOccurred() {
1199            String lgxName = _curLogixNG.getSystemName();
1200            _debugger.logixNGData.forEach((key, value) -> {
1201                if (key.equals("Finish")) {                  // NOI18N
1202                    _debugger = null;
1203                    _inEditConditionalNGMode = false;
1204                    _logixNGEditor.bringToFront();
1205                } else if (key.equals("Delete")) {           // NOI18N
1206                    deletePressed();
1207                } else if (key.equals("chgUname")) {         // NOI18N
1208                    LogixNG x = _logixNG_Manager.getBySystemName(lgxName);
1209                    if (x == null) {
1210                        log.error("Found no logixNG for name {} when changing user name (2)", lgxName);
1211                        return;
1212                    }
1213                    x.setUserName(value);
1214
1215                    if (beanTableDataModel != null) {
1216                        beanTableDataModel.fireTableDataChanged();
1217                    }
1218                }
1219            });
1220        }
1221    }
1222
1223
1224    // This class is copied from BeanTableDataModel
1225    private class DeleteBeanWorker extends SwingWorker<Void, Void> {
1226
1227        private final ConditionalNG _conditionalNG;
1228        private final int _row;
1229        boolean _hasDeleted = false;
1230
1231        public DeleteBeanWorker(ConditionalNG conditionalNG, int row) {
1232            _conditionalNG = conditionalNG;
1233            _row = row;
1234        }
1235
1236        public int getDisplayDeleteMsg() {
1237            return InstanceManager.getDefault(UserPreferencesManager.class).getMultipleChoiceOption(TreeEditor.class.getName(), "deleteInUse");
1238        }
1239
1240        public void setDisplayDeleteMsg(int boo) {
1241            InstanceManager.getDefault(UserPreferencesManager.class).setMultipleChoiceOption(TreeEditor.class.getName(), "deleteInUse", boo);
1242        }
1243
1244        public void doDelete() {
1245            try {
1246                InstanceManager.getDefault(ConditionalNG_Manager.class).deleteBean(_conditionalNG, "DoDelete");  // NOI18N
1247                _conditionalNGTableModel.fireTableRowsDeleted(_row, _row);
1248                _numConditionalNGs--;
1249                _showReminder = true;
1250                _hasDeleted = true;
1251            } catch (PropertyVetoException e) {
1252                //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto
1253                log.error("Unexpected doDelete failure for {}, {}", _conditionalNG, e.getMessage() );
1254            }
1255        }
1256
1257        /**
1258         * {@inheritDoc}
1259         */
1260        @Override
1261        public Void doInBackground() {
1262            _conditionalNG.getFemaleSocket().unregisterListeners();
1263
1264            StringBuilder message = new StringBuilder();
1265            try {
1266                InstanceManager.getDefault(ConditionalNG_Manager.class).deleteBean(_conditionalNG, "CanDelete");  // NOI18N
1267            } catch (PropertyVetoException e) {
1268                if (e.getPropertyChangeEvent().getPropertyName().equals("DoNotDelete")) { // NOI18N
1269                    log.warn("Do not Delete {}, {}", _conditionalNG, e.getMessage());
1270                    message.append(Bundle.getMessage("VetoDeleteBean", _conditionalNG.getBeanType(), _conditionalNG.getDisplayName(NamedBean.DisplayOptions.USERNAME_SYSTEMNAME), e.getMessage()));
1271                    JmriJOptionPane.showMessageDialog(null, message.toString(),
1272                            Bundle.getMessage("WarningTitle"),
1273                            JmriJOptionPane.ERROR_MESSAGE);
1274                    return null;
1275                }
1276                message.append(e.getMessage());
1277            }
1278            List<String> listenerRefs = new ArrayList<>();
1279            _conditionalNG.getListenerRefsIncludingChildren(listenerRefs);
1280            int listenerRefsCount = listenerRefs.size();
1281            log.debug("Delete with {}", listenerRefsCount);
1282            if (getDisplayDeleteMsg() == 0x02 && message.toString().isEmpty()) {
1283                doDelete();
1284            } else {
1285                final JDialog dialog = new JDialog();
1286                dialog.setTitle(Bundle.getMessage("WarningTitle"));
1287                dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1288                JPanel container = new JPanel();
1289                container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
1290                container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
1291
1292                if (listenerRefsCount > 0) { // warn of listeners attached before delete
1293                    String prompt = _conditionalNG.getFemaleSocket().isConnected()
1294                            ? "DeleteWithChildrenPrompt" : "DeletePrompt";
1295                    JLabel question = new JLabel(Bundle.getMessage(prompt, _conditionalNG.getDisplayName(NamedBean.DisplayOptions.USERNAME_SYSTEMNAME)));
1296                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
1297                    container.add(question);
1298
1299                    ArrayList<String> listeners = new ArrayList<>();
1300                    for (String listenerRef : listenerRefs) {
1301                        if (!listeners.contains(listenerRef)) {
1302                            listeners.add(listenerRef);
1303                        }
1304                    }
1305
1306                    message.append("<br>");
1307                    message.append(Bundle.getMessage("ReminderInUse", listenerRefsCount));
1308                    message.append("<ul>");
1309                    for (String listener : listeners) {
1310                        message.append("<li>");
1311                        message.append(listener);
1312                        message.append("</li>");
1313                    }
1314                    message.append("</ul>");
1315
1316                    JEditorPane pane = new JEditorPane();
1317                    pane.setContentType("text/html");
1318                    pane.setText("<html>" + message.toString() + "</html>");
1319                    pane.setEditable(false);
1320                    JScrollPane jScrollPane = new JScrollPane(pane);
1321                    container.add(jScrollPane);
1322                } else {
1323                    String prompt = _conditionalNG.getFemaleSocket().isConnected()
1324                            ? "DeleteWithChildrenPrompt" : "DeletePrompt";
1325                    String msg = MessageFormat.format(
1326                            Bundle.getMessage(prompt), _conditionalNG.getSystemName());
1327                    JLabel question = new JLabel(msg);
1328                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
1329                    container.add(question);
1330                }
1331
1332                final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
1333                remember.setFont(remember.getFont().deriveFont(10f));
1334                remember.setAlignmentX(Component.CENTER_ALIGNMENT);
1335
1336                JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
1337                JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
1338                JPanel button = new JPanel();
1339                button.setAlignmentX(Component.CENTER_ALIGNMENT);
1340                button.add(yesButton);
1341                button.add(noButton);
1342                container.add(button);
1343
1344                noButton.addActionListener((ActionEvent e) -> {
1345                    //there is no point in remembering this the user will never be
1346                    //able to delete a bean!
1347                    dialog.dispose();
1348                });
1349
1350                yesButton.addActionListener((ActionEvent e) -> {
1351                    if (remember.isSelected()) {
1352                        setDisplayDeleteMsg(0x02);
1353                    }
1354                    doDelete();
1355                    dialog.dispose();
1356                });
1357                container.add(remember);
1358                container.setAlignmentX(Component.CENTER_ALIGNMENT);
1359                container.setAlignmentY(Component.CENTER_ALIGNMENT);
1360                dialog.getContentPane().add(container);
1361                dialog.pack();
1362
1363                dialog.getRootPane().setDefaultButton(noButton);
1364                noButton.requestFocusInWindow(); // set default keyboard focus, after pack() before setVisible(true)
1365                dialog.getRootPane().registerKeyboardAction(e -> { // escape to exit
1366                        dialog.setVisible(false);
1367                        dialog.dispose(); },
1368                    KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
1369
1370                dialog.setLocation((Toolkit.getDefaultToolkit().getScreenSize().width) / 2 - dialog.getWidth() / 2, (Toolkit.getDefaultToolkit().getScreenSize().height) / 2 - dialog.getHeight() / 2);
1371                dialog.setModal(true);
1372                dialog.setVisible(true);
1373            }
1374            if (!_hasDeleted && _conditionalNG.getFemaleSocket().isActive()) _conditionalNG.getFemaleSocket().registerListeners();
1375            return null;
1376        }
1377
1378        /**
1379         * {@inheritDoc} Minimal implementation to catch and log errors
1380         */
1381        @Override
1382        protected void done() {
1383            try {
1384                get();  // called to get errors
1385            } catch (InterruptedException | java.util.concurrent.ExecutionException e) {
1386                log.error("Exception while deleting bean", e);
1387            }
1388        }
1389    }
1390
1391
1392    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNGEditor.class);
1393
1394}