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