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