001package jmri.jmrit.conditional;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.Component;
006import java.awt.Container;
007import java.awt.GridBagConstraints;
008import java.awt.GridBagLayout;
009import java.awt.event.*;
010import java.util.ArrayList;
011import java.util.List;
012import java.util.TreeMap;
013import java.util.TreeSet;
014import javax.swing.*;
015import javax.swing.event.*;
016import javax.swing.tree.*;
017
018import org.apache.commons.lang3.StringUtils;
019
020import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
021
022import javax.swing.filechooser.FileNameExtensionFilter;
023
024import jmri.*;
025import jmri.Conditional.Operator;
026import jmri.implementation.DefaultConditional;
027import jmri.implementation.DefaultConditionalAction;
028import jmri.jmrit.beantable.LRouteTableAction;
029import jmri.jmrit.logix.OBlock;
030import jmri.jmrit.logix.Warrant;
031import jmri.script.swing.ScriptFileChooser;
032import jmri.swing.NamedBeanComboBox;
033import jmri.util.FileUtil;
034import jmri.util.JmriJFrame;
035import jmri.util.swing.JComboBoxUtil;
036import jmri.util.swing.JmriJOptionPane;
037
038/**
039 * A tree based editor for maintaining Logix Conditionals, State Variables and
040 * Actions.
041 * <p>
042 * The tree has 3 levels. The first level are the conditionals contained in the
043 * selected Logix. The second level contains the antecedent, logic type and
044 * trigger mode settings. The third level contains the detail Variable and
045 * Action lines.
046 * <p>
047 * Compare with the other Conditional Edit tool {@link ConditionalListEdit}
048 *
049 * @author Dave Sand copyright (c) 2017
050 */
051public class ConditionalTreeEdit extends ConditionalEditBase {
052
053    /**
054     * Create a new Conditional Tree View editor.
055     *
056     * @param sName The system name of the current Logix
057     */
058    public ConditionalTreeEdit(String sName) {
059        super(sName);
060        buildConditionalComponents();
061        buildActionComponents();
062        buildVariableComponents();
063        makeEditLogixWindow();
064        setFocusListeners();
065        setEditMode(false);
066    }
067
068    public ConditionalTreeEdit() {
069    }
070
071    JPanel _curDetailPanel = new JPanel();
072    JTextField _editLogixUserName = new JTextField(20);   // Logix User Name field
073    NamedBeanComboBox<?> _comboNameBox = null;
074
075
076    // ------------ Edit detail components ------------
077    JPanel _detailGrid = new JPanel();
078    JPanel _detailFooter = new JPanel();
079    JPanel _gridPanel;  // Child of _detailGrid, contains the current grid labels and fields
080    JTextField _editConditionalUserName;
081    JTextField _editAntecedent;
082    JComboBox<Conditional.AntecedentOperator> _editOperatorMode;
083    boolean _editActive = false;
084    JButton _cancelAction;
085    JButton _updateAction;
086    String _pickCommand = null;
087    Conditional.ItemType _pickItem = Conditional.ItemType.NONE;
088
089    // ------------ Tree variables ------------
090    JTree _cdlTree;
091    DefaultTreeModel _cdlModel;
092    DefaultMutableTreeNode _cdlRoot;
093    TreeSelectionListener _cdlListener;
094    TreePath _curTreePath = null;
095
096    // ------------ Tree components ------------
097    ConditionalTreeNode _cdlNode = null;
098    ConditionalTreeNode _varHead = null;
099    ConditionalTreeNode _varNode = null;
100    ConditionalTreeNode _actHead = null;
101    ConditionalTreeNode _actNode = null;
102    ConditionalTreeNode _leafNode = null;
103
104    // ------------ Current tree node variables ------------
105    ConditionalTreeNode _curNode = null;
106    String _curNodeName = null;
107    String _curNodeType = null;
108    String _curNodeText = null;
109    int _curNodeRow = -1;
110
111    // ------------ Button bar components ------------
112    JPanel _leftButtonBar;
113    JPanel _labelPanel;
114    JPanel _addButtonPanel;
115    JPanel _toggleButtonPanel;
116    JPanel _checkButtonPanel;
117    JPanel _moveButtonPanel;
118    JPanel _deleteButtonPanel;
119    JPanel _helpButtonPanel;
120
121    JLabel _conditionalLabel = new JLabel(Bundle.getMessage("LabelConditionalActions"));  // NOI18N
122    JLabel _antecedentLabel = new JLabel(Bundle.getMessage("LabelAntecedentActions"));  // NOI18N
123    JLabel _logicTypeLabel = new JLabel(Bundle.getMessage("LabelLogicTypeActions"));  // NOI18N
124    JLabel _triggerModeLabel = new JLabel(Bundle.getMessage("LabelTriggerModeActions"));  // NOI18N
125    JLabel _variablesLabel = new JLabel(Bundle.getMessage("LabelVariablesActions"));  // NOI18N
126    JLabel _variableLabel = new JLabel(Bundle.getMessage("LabelVariableActions"));  // NOI18N
127    JLabel _actionsLabel = new JLabel(Bundle.getMessage("LabelActionsActions"));  // NOI18N
128    JLabel _actionLabel = new JLabel(Bundle.getMessage("LabelActionActions"));  // NOI18N
129
130    // ------------ Current conditional components ------------
131    Conditional _curConditional;
132    List<ConditionalVariable> _variableList;   // Current Variable List
133    List<ConditionalAction> _actionList;       // Current Action List
134    ConditionalVariable _curVariable;               // Current Variable
135    ConditionalAction _curAction;                   // Current Action
136    Conditional.ItemType _curVariableItem = Conditional.ItemType.NONE;
137    Conditional.ItemType _curActionItem = Conditional.ItemType.NONE;
138    String _curConditionalName = "";
139    String _antecedent;
140    Conditional.AntecedentOperator _logicType;
141    boolean _triggerMode;
142    boolean _newActionItem = false;
143    boolean _newVariableItem = false;
144    TreeSet<String> _oldTargetNames = new TreeSet<>();
145
146    // ------------ Select Conditional Variables ------------
147    JComboBox<String> _selectLogixBox = new JComboBox<>();
148    JComboBox<String> _selectConditionalBox = new JComboBox<>();
149    TreeMap<String, String> _selectLogixMap = new TreeMap<>();
150    ArrayList<String> _selectConditionalList = new ArrayList<>();
151
152    // ------------ Components of Edit Variable pane ------------
153    JComboBox<Conditional.ItemType> _variableItemBox;
154    JComboBox<Conditional.Type> _variableStateBox;
155    JComboBox<String> _variableOperBox;
156    JCheckBox _variableNegated;
157    JCheckBox _variableTriggerActions;
158    JTextField _variableNameField;
159    JLabel _variableNameLabel = new JLabel(Bundle.getMessage("LabelItemName"));  // NOI18N
160    JComboBox<String> _variableCompareOpBox;
161    JComboBox<String> _variableSignalBox;
162    JComboBox<Conditional.Type> _variableCompareTypeBox;
163    JLabel _variableMemoryValueLabel = new JLabel("");
164    JTextField _variableData1Field;
165    JTextField _variableData2Field;
166
167    // ------------ Components of Edit Action pane ------------
168    JComboBox<Conditional.ItemType> _actionItemBox;
169    JComboBox<Conditional.Action> _actionTypeBox;
170    JLabel _actionTypeLabel = new JLabel("Type");  // NOI18N
171    JTextField _actionNameField;
172    JLabel _actionNameLabel = new JLabel("Name");  // NOI18N
173    JComboBox<String> _actionBox;
174    JLabel _actionBoxLabel = new JLabel("Box");  // NOI18N
175    JTextField _longActionString;
176    JLabel _longActionLabel = new JLabel("Long");  // NOI18N
177    JTextField _shortActionString;
178    JLabel _shortActionLabel = new JLabel("Short");  // NOI18N
179    JComboBox<String> _actionOptionBox;
180    JButton _actionSetButton;
181
182    // ============  Edit conditionals for the current Logix ============
183
184    /**
185     * Create the edit logix window.
186     * <p>
187     * The left side contains a tree structure containing the conditionals for
188     * the current Logix. The right side contains detail edit panes based on the
189     * current tree row selection.
190     */
191    void makeEditLogixWindow() {
192        _editLogixFrame = new JmriJFrame(Bundle.getMessage("TitleEditLogix"));  // NOI18N
193        _editLogixFrame.addHelpMenu(
194                "package.jmri.jmrit.conditional.ConditionalTreeEditor", true);  // NOI18N
195        Container contentPane = _editLogixFrame.getContentPane();
196        contentPane.setLayout(new BorderLayout());
197
198        // ------------ Header ------------
199        JPanel header = new JPanel();
200        JPanel logixNames = new JPanel();
201        logixNames.setLayout(new BoxLayout(logixNames, BoxLayout.X_AXIS));
202
203        JLabel systemNameLabel = new JLabel(Bundle.getMessage("ColumnSystemName") + ":");  // NOI18N
204        logixNames.add(systemNameLabel);
205        logixNames.add(Box.createHorizontalStrut(5));
206
207        JLabel fixedSystemName = new JLabel(_curLogix.getSystemName());
208        logixNames.add(fixedSystemName);
209        logixNames.add(Box.createHorizontalStrut(20));
210
211        JLabel userNameLabel = new JLabel(Bundle.getMessage("ColumnUserName") + ":");  // NOI18N
212        logixNames.add(userNameLabel);
213        logixNames.add(Box.createHorizontalStrut(5));
214
215        _editLogixUserName.setText(_curLogix.getUserName());
216        logixNames.add(_editLogixUserName);
217        _editLogixUserName.setToolTipText(Bundle.getMessage("LogixUserNameHint2"));  // NOI18N
218        _editLogixUserName.addActionListener(new ActionListener() {
219            @Override
220            public void actionPerformed(ActionEvent e) {
221                String uName = _editLogixUserName.getText().trim();
222                if (!(uName.equals(_curLogix.getUserName()))) {
223                    // user name has changed - check if already in use
224                    if (uName.length() > 0) {
225                        Logix p = _logixManager.getByUserName(uName);
226                        if (p != null) {
227                            // Logix with this user name already exists
228                            log.error("Failure to update Logix with Duplicate User Name: {}", uName); // NOI18N
229                            JmriJOptionPane.showMessageDialog(_editLogixFrame,
230                                    Bundle.getMessage("Error6"), Bundle.getMessage("ErrorTitle"), // NOI18N
231                                    JmriJOptionPane.ERROR_MESSAGE);
232                            return;
233                        }
234                    }
235                    // user name is unique, change it
236                    logixData.clear();
237                    logixData.put("chgUname", uName);  // NOI18N
238                    fireLogixEvent();
239                    _showReminder = true;
240                }
241            }
242        });
243
244        header.add(logixNames);
245        contentPane.add(header, BorderLayout.NORTH);
246
247        // ------------ body - tree (left side) ------------
248        JTree treeContent = buildConditionalTree();
249        JScrollPane treeScroll = new JScrollPane(treeContent);
250
251        // ------------ body - detail (right side) ------------
252        JPanel detailPane = new JPanel();
253        detailPane.setBorder(BorderFactory.createMatteBorder(0, 2, 0, 0, Color.DARK_GRAY));
254        detailPane.setLayout(new BoxLayout(detailPane, BoxLayout.Y_AXIS));
255
256        // ------------ Edit Detail Panel ------------
257        makeDetailGrid("EmptyGrid");  // NOI18N
258
259        JPanel panel = new JPanel();
260        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
261
262        _cancelAction = new JButton(Bundle.getMessage("ButtonCancel"));  // NOI18N
263        _cancelAction.setToolTipText(Bundle.getMessage("HintCancelButton"));  // NOI18N
264        panel.add(_cancelAction);
265        _cancelAction.addActionListener(new ActionListener() {
266            @Override
267            public void actionPerformed(ActionEvent e) {
268                cancelPressed();
269            }
270        });
271        panel.add(Box.createHorizontalStrut(10));
272
273        _updateAction = new JButton(Bundle.getMessage("ButtonUpdate"));  // NOI18N
274        _updateAction.setToolTipText(Bundle.getMessage("UpdateButtonHint"));  // NOI18N
275        panel.add(_updateAction);
276        _updateAction.addActionListener(new ActionListener() {
277            @Override
278            public void actionPerformed(ActionEvent e) {
279                updatePressed();
280            }
281        });
282        _detailFooter.add(panel);
283
284        JPanel detailEdit = new JPanel(new BorderLayout());
285        detailEdit.add(_detailGrid, BorderLayout.NORTH);
286        detailEdit.add(_detailFooter, BorderLayout.SOUTH);
287        detailPane.add(detailEdit);
288        _editLogixUserName.setEnabled(true);
289
290        JSplitPane bodyPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeScroll, detailPane);
291        bodyPane.setDividerSize(10);
292        bodyPane.setResizeWeight(.35);
293        bodyPane.setOneTouchExpandable(true);
294        contentPane.add(bodyPane);
295
296        // ------------ footer ------------
297        JPanel footer = new JPanel(new BorderLayout());
298        _labelPanel = new JPanel();
299        _labelPanel.add(_conditionalLabel);
300        _leftButtonBar = new JPanel();
301        _leftButtonBar.add(_labelPanel);
302
303        // ------------ Add Button ------------
304        JButton addButton = new JButton(Bundle.getMessage("ButtonAddText")); // NOI18N
305        addButton.setToolTipText(Bundle.getMessage("HintAddButton"));        // NOI18N
306        addButton.addActionListener(new ActionListener() {
307            @Override
308            public void actionPerformed(ActionEvent e) {
309                addPressed();
310            }
311        });
312        _addButtonPanel = new JPanel();
313        _addButtonPanel.add(addButton);
314        _leftButtonBar.add(_addButtonPanel);
315
316        // ------------ Help Button ------------
317        JButton helpButton = new JButton(Bundle.getMessage("ButtonHelp"));  // NOI18N
318        helpButton.setToolTipText(Bundle.getMessage("HintHelpButton"));     // NOI18N
319        helpButton.addActionListener(new ActionListener() {
320            @Override
321            public void actionPerformed(ActionEvent e) {
322                helpPressed();
323            }
324        });
325        _helpButtonPanel = new JPanel();
326        _helpButtonPanel.add(helpButton);
327        _helpButtonPanel.setVisible(false);
328        _leftButtonBar.add(_helpButtonPanel);
329
330        // ------------ Toggle Button ------------
331        JButton toggleButton = new JButton(Bundle.getMessage("ButtonToggle"));  // NOI18N
332        toggleButton.setToolTipText(Bundle.getMessage("HintToggleButton"));     // NOI18N
333        toggleButton.addActionListener(new ActionListener() {
334            @Override
335            public void actionPerformed(ActionEvent e) {
336                togglePressed();
337            }
338        });
339        _toggleButtonPanel = new JPanel();
340        _toggleButtonPanel.add(toggleButton);
341        _toggleButtonPanel.setVisible(false);
342        _leftButtonBar.add(_toggleButtonPanel);
343
344        // ------------ Check Button ------------
345        JButton checkButton = new JButton(Bundle.getMessage("ButtonCheck"));  // NOI18N
346        checkButton.setToolTipText(Bundle.getMessage("HintCheckButton"));     // NOI18N
347        checkButton.addActionListener(new ActionListener() {
348            @Override
349            public void actionPerformed(ActionEvent e) {
350                checkPressed();
351            }
352        });
353        _checkButtonPanel = new JPanel();
354        _checkButtonPanel.add(checkButton);
355        _checkButtonPanel.setVisible(true);
356        _leftButtonBar.add(_checkButtonPanel);
357
358        // ------------ Delete Button ------------
359        JButton deleteButton = new JButton(Bundle.getMessage("ButtonDelete")); // NOI18N
360        deleteButton.setToolTipText(Bundle.getMessage("HintDeleteButton"));    // NOI18N
361        deleteButton.addActionListener(new ActionListener() {
362            @Override
363            public void actionPerformed(ActionEvent e) {
364                deletePressed();
365            }
366        });
367        _deleteButtonPanel = new JPanel();
368        _deleteButtonPanel.add(deleteButton);
369        _deleteButtonPanel.setVisible(false);
370        _leftButtonBar.add(_deleteButtonPanel);
371
372        footer.add(_leftButtonBar, BorderLayout.WEST);
373        JPanel rightButtonBar = new JPanel();
374
375        // ------------ Move Buttons ------------
376        JLabel moveLabel = new JLabel(Bundle.getMessage("LabelMove"));      // NOI18N
377
378        JButton upButton = new JButton(Bundle.getMessage("ButtonUp"));      // NOI18N
379        upButton.setToolTipText(Bundle.getMessage("HintUpButton"));         // NOI18N
380        JButton downButton = new JButton(Bundle.getMessage("ButtonDown"));  // NOI18N
381        downButton.setToolTipText(Bundle.getMessage("HintDownButton"));     // NOI18N
382
383        upButton.addActionListener(new ActionListener() {
384            @Override
385            public void actionPerformed(ActionEvent e) {
386                downButton.setEnabled(false);
387                upButton.setEnabled(false);
388                upPressed();
389            }
390        });
391
392        downButton.addActionListener(new ActionListener() {
393            @Override
394            public void actionPerformed(ActionEvent e) {
395                upButton.setEnabled(false);
396                downButton.setEnabled(false);
397                downPressed();
398            }
399        });
400
401        _moveButtonPanel = new JPanel();
402        _moveButtonPanel.add(moveLabel);
403        _moveButtonPanel.add(upButton);
404        _moveButtonPanel.add(new JLabel("|"));
405        _moveButtonPanel.add(downButton);
406        _moveButtonPanel.setVisible(false);
407        _leftButtonBar.add(_moveButtonPanel);
408
409        // ------------ Done Button ------------
410        JButton doneButton = new JButton(Bundle.getMessage("ButtonDone"));  // NOI18N
411        doneButton.setToolTipText(Bundle.getMessage("HintDoneButton"));     // NOI18N
412        doneButton.addActionListener(new ActionListener() {
413            @Override
414            public void actionPerformed(ActionEvent e) {
415                donePressed();
416            }
417        });
418        JPanel doneButtonPanel = new JPanel();
419        doneButtonPanel.add(doneButton);
420        rightButtonBar.add(doneButtonPanel);
421
422        footer.add(rightButtonBar, BorderLayout.EAST);
423        contentPane.add(footer, BorderLayout.SOUTH);
424
425        _editLogixFrame.addWindowListener(new java.awt.event.WindowAdapter() {
426            @Override
427            public void windowClosing(java.awt.event.WindowEvent e) {
428                donePressed();
429            }
430        });
431        _editLogixFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
432        _editLogixFrame.pack();
433        _editLogixFrame.setVisible(true);
434    }
435
436    /**
437     * Initialize conditional components.
438     */
439    void buildConditionalComponents() {
440        _editConditionalUserName = new JTextField(20);
441        _editAntecedent = new JTextField(20);
442        _editOperatorMode = new JComboBox<>();
443        for (Conditional.AntecedentOperator operator : Conditional.AntecedentOperator.values()) {
444            _editOperatorMode.addItem(operator);
445        }
446    }
447
448    // ------------ Create Conditional GridBag panels ------------
449
450    /**
451     * Build new GridBag content. The grid panel is hidden, emptied, re-built and
452     * made visible.
453     *
454     * @param gridType The type of grid to create
455     */
456    void makeDetailGrid(String gridType) {
457        _detailGrid.setVisible(false);
458        _detailGrid.removeAll();
459        _detailFooter.setVisible(true);
460
461        _gridPanel = new JPanel(new GridBagLayout());
462        GridBagConstraints c = new GridBagConstraints();
463        c.gridwidth = 1;
464        c.gridheight = 1;
465        c.ipadx = 5;
466
467        switch (gridType) {
468            case "EmptyGrid":  // NOI18N
469                makeEmptyGrid(c);
470                _detailFooter.setVisible(false);
471                break;
472
473            // ------------ Conditional Edit Grids ------------
474            case "Conditional":  // NOI18N
475                makeConditionalGrid(c);
476                break;
477
478            case "Antecedent":  // NOI18N
479                makeAntecedentGrid(c);
480                break;
481
482            case "LogicType":  // NOI18N
483                makeLogicTypeGrid(c);
484                break;
485
486            // ------------ Variable Edit Grids ------------
487            case "EmptyVariable":  // NOI18N
488                makeEmptyVariableGrid(c);
489                break;
490
491            case "StandardVariable":  // NOI18N
492                makeStandardVariableGrid(c);
493                break;
494
495            case "SignalAspectVariable":  // NOI18N
496                makeSignalAspectVariableGrid(c);
497                break;
498
499            case "ConditionalVariable":  // NOI18N
500                makeConditionalVariableGrid(c);
501                break;
502
503            case "MemoryVariable":  // NOI18N
504                makeMemoryVariableGrid(c);
505                break;
506
507            case "FastClockVariable":  // NOI18N
508                makeFastClockVariableGrid(c);
509                break;
510
511            // ------------ Action Edit Grids ------------
512            case "EmptyAction":  // NOI18N
513                makeEmptyActionGrid(c);
514                break;
515
516            case "NameTypeAction":  // NOI18N
517                makeNameTypeActionGrid(c, false);   // Skip change/trigger row
518                break;
519
520            case "NameTypeActionFinal":  // NOI18N
521                makeNameTypeActionGrid(c, true);    // Include change/trigger row
522                break;
523
524            case "TypeAction":  // NOI18N
525                makeTypeActionGrid(c, false);       // Skip change/trigger row
526                break;
527
528            case "TypeActionFinal":  // NOI18N
529                makeTypeActionGrid(c, true);        // Include change/trigger row
530                break;
531
532            case "TypeShortAction":  // NOI18N
533                makeTypeShortActionGrid(c);
534                break;
535
536            case "StandardAction":  // NOI18N
537                makeStandardActionGrid(c, true);    // Include change/trigger row
538                break;
539
540            case "ShortFieldAction":  // NOI18N
541                makeShortFieldActionGrid(c, true);  // Include Action Box
542                break;
543
544            case "ShortFieldNoBoxAction":  // NOI18N
545                makeShortFieldActionGrid(c, false); // Skip Action Box
546                break;
547
548            case "FileAction":  // NOI18N
549                makeFileActionGrid(c);
550                break;
551
552            default:
553                log.warn("Invalid grid type: '{}'", gridType);  // NOI18N
554                makeEmptyGrid(c);
555        }
556
557        _detailGrid.add(_gridPanel);
558        _detailGrid.setVisible(true);
559    }
560
561    /**
562     * This grid is used when there are no edit grids required.
563     *
564     * @param c The constraints object used for the grid construction
565     */
566    void makeEmptyGrid(GridBagConstraints c) {
567        // Variable type box
568        c.gridy = 0;
569        c.gridx = 0;
570        c.anchor = java.awt.GridBagConstraints.CENTER;
571        JLabel row0Label = new JLabel("This page is intentionally blank");  // NOI18N
572        _gridPanel.add(row0Label, c);
573    }
574
575    /**
576     * This grid is used to edit the Conditional User Name.
577     *
578     * @param c The constraints object used for the grid construction
579     */
580    void makeConditionalGrid(GridBagConstraints c) {
581        c.gridy = 0;
582        c.gridx = 0;
583        c.anchor = java.awt.GridBagConstraints.EAST;
584        JLabel row0Label = new JLabel(Bundle.getMessage("ConditionalUserName"));  // NOI18N
585        row0Label.setToolTipText(Bundle.getMessage("ConditionalUserNameHint"));  // NOI18N
586        _gridPanel.add(row0Label, c);
587        c.gridx = 1;
588        c.anchor = java.awt.GridBagConstraints.WEST;
589        _gridPanel.add(_editConditionalUserName, c);
590    }
591
592    /**
593     * This grid is used to edit the Antecedent when the Logic Type is Mixed.
594     *
595     * @param c The constraints object used for the grid construction
596     */
597    void makeAntecedentGrid(GridBagConstraints c) {
598        c.gridy = 0;
599        c.gridx = 0;
600        c.anchor = java.awt.GridBagConstraints.EAST;
601        JLabel row0Label = new JLabel(Bundle.getMessage("LabelAntecedentHeader"));  // NOI18N
602        row0Label.setToolTipText(Bundle.getMessage("LabelAntecedentHint"));  // NOI18N
603        _gridPanel.add(row0Label, c);
604        c.gridx = 1;
605        c.anchor = java.awt.GridBagConstraints.WEST;
606        _gridPanel.add(_editAntecedent, c);
607    }
608
609    /**
610     * This grid is used to edit the Logic Type.
611     *
612     * @param c The constraints object used for the grid construction
613     */
614    void makeLogicTypeGrid(GridBagConstraints c) {
615        c.gridy = 0;
616        c.gridx = 0;
617        c.anchor = java.awt.GridBagConstraints.EAST;
618        JLabel row0Label = new JLabel(Bundle.getMessage("LabelLogicType"));  // NOI18N
619        row0Label.setToolTipText(Bundle.getMessage("TypeLogicHint"));  // NOI18N
620        _gridPanel.add(row0Label, c);
621        c.gridx = 1;
622        c.anchor = java.awt.GridBagConstraints.WEST;
623        _gridPanel.add(_editOperatorMode, c);
624    }
625
626    // ------------ Process button bar and tree events ------------
627
628    /**
629     * Add new items: Conditionals, Variables or Actions.
630     */
631    void addPressed() {
632        if (_curNode == null) {
633            // New conditional with no prior selection
634            _curNodeType = "Conditional";  // NOI18N
635        }
636
637        switch (_curNodeType) {
638            case "Conditional":     // NOI18N
639                // make system name for new conditional
640                int num = _curLogix.getNumConditionals() + 1;
641                _curConditional = null;
642                String cName = null;
643                while (_curConditional == null) {
644                    cName = _curLogix.getSystemName() + "C" + Integer.toString(num);
645                    _curConditional = _conditionalManager.createNewConditional(cName, "");
646                    num++;
647                    if (num == 1000) {
648                        break;
649                    }
650                }
651                if (_curConditional == null) {
652                    // should never get here unless there is an assignment conflict
653                    log.error("Failure to create Conditional with System Name: {}", cName);  // NOI18N
654                    return;
655                }
656                // add to Logix at the end of the calculate order
657                _curLogix.addConditional(cName, -1);
658                _actionList = new ArrayList<>();
659                _variableList = new ArrayList<>();
660                _curConditional.setAction(_actionList);
661                _curConditional.setStateVariables(_variableList);
662                _showReminder = true;
663
664                // Build tree components
665                Conditional curConditional = _curLogix.getConditional(cName);
666                _curNode = new ConditionalTreeNode(buildNodeText("Conditional", curConditional, 0), "Conditional", cName, // NOI18N
667                        _curLogix.getNumConditionals() - 1);
668                _cdlRoot.add(_curNode);
669                _leafNode = new ConditionalTreeNode(buildNodeText("Antecedent", curConditional, 0), "Antecedent", cName, 0);   // NOI18N
670                _curNode.add(_leafNode);
671                _varHead = new ConditionalTreeNode(buildNodeText("Variables", curConditional, 0), "Variables", cName, 0);     // NOI18N
672                _curNode.add(_varHead);
673                _leafNode = new ConditionalTreeNode(buildNodeText("LogicType", curConditional, 0), "LogicType", cName, 0);      // NOI18N
674                _curNode.add(_leafNode);
675                _triggerMode = curConditional.getTriggerOnChange();
676                _leafNode = new ConditionalTreeNode(buildNodeText("TriggerMode", curConditional, 0), "TriggerMode", cName, 0);      // NOI18N
677                _curNode.add(_leafNode);
678                _actHead = new ConditionalTreeNode(buildNodeText("Actions", curConditional, 0), "Actions", cName, 0);      // NOI18N
679                _curNode.add(_actHead);
680                _cdlModel.nodeStructureChanged(_cdlRoot);
681
682                // Switch to new node
683                _cdlTree.setSelectionPath(new TreePath(_curNode.getPath()));
684                break;
685
686            case "Variables":    // NOI18N
687                newVariable();
688                break;
689
690            case "Variable":     // NOI18N
691                newVariable();
692                break;
693
694            case "Actions":    // NOI18N
695                newAction();
696                break;
697
698            case "Action":     // NOI18N
699                newAction();
700                break;
701
702            default:
703                log.error("Add called for unsupported node type: '{}'", _curNodeType);  // NOI18N
704        }
705    }
706
707    /**
708     * Create a new variable Can be invoked by a Variables or Variable node.
709     */
710    void newVariable() {
711        if (LRouteTableAction.getLogixInitializer().equals(_curLogix.getSystemName())) {
712           JmriJOptionPane.showMessageDialog(_editLogixFrame,
713                    Bundle.getMessage("Error49"), Bundle.getMessage("ErrorTitle"), // NOI18N
714                    JmriJOptionPane.ERROR_MESSAGE);
715            return;
716        }
717
718        cancelPressed();    // Make sure that there are no active edit sessions
719        _showReminder = true;
720        _curVariableItem = Conditional.ItemType.NONE;
721        ConditionalVariable variable = new ConditionalVariable();
722        _variableList.add(variable);
723        _newVariableItem = true;
724        setMoveButtons();   // Buttons will be disabled
725
726        int size = _variableList.size();
727        _curVariable = _variableList.get(size - 1);
728        // default of operator for postion 0 (row 1) is Conditional.OPERATOR_NONE
729        if (size > 1) {
730            if (_logicType == Conditional.AntecedentOperator.ALL_OR) {
731                _curVariable.setOpern(Conditional.Operator.OR);
732            } else {
733                _curVariable.setOpern(Conditional.Operator.AND);
734            }
735        }
736        appendToAntecedent();
737        size--;
738
739        // Update tree structure
740        if (_curNodeType.equals("Variables")) {  // NOI18N
741            _varHead = _curNode;
742        } else {
743            _varHead = (ConditionalTreeNode) _curNode.getParent();
744        }
745        _leafNode = new ConditionalTreeNode(buildNodeText("Variable", _curVariable, size), "Variable", _curNodeName, size);  // NOI18N
746        _varHead.add(_leafNode);
747        _cdlModel.nodeStructureChanged(_curNode);
748        _varHead.setRow(size + 1);
749        _cdlModel.nodeStructureChanged(_varHead);
750
751        // Switch to new node
752        ConditionalTreeNode tempNode = (ConditionalTreeNode) _varHead.getLastChild();
753        TreePath newPath = new TreePath(tempNode.getPath());
754        _cdlTree.setSelectionPath(newPath);
755        _cdlTree.expandPath(newPath);
756    }
757
758    /**
759     * Create a new action Can be invoked by a Actions or Action node.
760     */
761    void newAction() {
762        cancelPressed();    // Make sure that there are no active edit sessions
763        _showReminder = true;
764        _curActionItem = Conditional.ItemType.NONE;
765        ConditionalAction action = new DefaultConditionalAction();
766        _actionList.add(action);
767        _newActionItem = true;
768        setMoveButtons();   // Buttons will be disabled
769
770        int size = _actionList.size();
771        _curAction = _actionList.get(size - 1);
772        size--;
773
774        // Update tree structure
775        if (_curNodeType.equals("Actions")) {  // NOI18N
776            _actHead = _curNode;
777        } else {
778            _actHead = (ConditionalTreeNode) _curNode.getParent();
779        }
780        _leafNode = new ConditionalTreeNode(buildNodeText("Action", _curAction, size), "Action", _curNodeName, size);  // NOI18N
781        _actHead.add(_leafNode);
782        _cdlModel.nodeStructureChanged(_curNode);
783        _actHead.setRow(size + 1);
784        _cdlModel.nodeStructureChanged(_actHead);
785
786        // Switch to new node
787        ConditionalTreeNode tempNode = (ConditionalTreeNode) _actHead.getLastChild();
788        TreePath newPath = new TreePath(tempNode.getPath());
789        _cdlTree.setSelectionPath(newPath);
790        _cdlTree.expandPath(newPath);
791    }
792
793    /**
794     * Set up the edit environment for the selected node Called from
795     * {@link #treeRowSelected}. This takes the place of an actual button.
796     */
797    void editPressed() {
798        switch (_curNodeType) {
799            case "Conditional":     // NOI18N
800                _editConditionalUserName.setText(_curConditional.getUserName());
801                makeDetailGrid("Conditional");  // NOI18N
802                break;
803
804            case "Antecedent":      // NOI18N
805                Conditional.AntecedentOperator chkLogicType = _curConditional.getLogicType();
806                if (chkLogicType != Conditional.AntecedentOperator.MIXED) {
807                    makeDetailGrid("EmptyGrid");  // NOI18N
808                    return;
809                }
810                _labelPanel.add(_antecedentLabel);
811                _helpButtonPanel.setVisible(true);
812                _editAntecedent.setText(translateAntecedent(_curConditional.getAntecedentExpression(), false));
813                makeDetailGrid("Antecedent");  // NOI18N
814                break;
815
816            case "LogicType":       // NOI18N
817                Conditional.AntecedentOperator curLogicType = _curConditional.getLogicType();
818                _editOperatorMode.setSelectedItem(curLogicType);
819                makeDetailGrid("LogicType");  // NOI18N
820                break;
821
822            case "Variable":     // NOI18N
823                _labelPanel.add(_variableLabel);
824                _curVariable = _variableList.get(_curNodeRow);
825                _curVariableItem = _curVariable.getType().getItemType();
826                initializeStateVariables();
827                if (_logicType != Conditional.AntecedentOperator.MIXED) {
828                    setMoveButtons();
829                }
830                _oldTargetNames.clear();
831                loadReferenceNames(_variableList, _oldTargetNames);
832                break;
833
834            case "Action":     // NOI18N
835                _labelPanel.add(_actionLabel);
836                _curAction = _actionList.get(_curNodeRow);
837                _actionOptionBox.removeAllItems();
838                for (int i = 1; i <= Conditional.NUM_ACTION_OPTIONS; i++) {
839                    _actionOptionBox.addItem(DefaultConditionalAction.getOptionString(i, _triggerMode));
840                }
841                _curActionItem = _curAction.getType().getItemType();
842                initializeActionVariables();
843                setMoveButtons();
844                break;
845
846            default:
847                log.error("Edit called for unsupported node type: '{}'", _curNodeType);  // NOI18N
848        }
849    }
850
851    /**
852     * Apply the updates to the current node.
853     */
854    void updatePressed() {
855        switch (_curNodeType) {
856            case "Conditional":     // NOI18N
857                userNameChanged(_editConditionalUserName.getText().trim());
858                break;
859
860            case "Antecedent":      // NOI18N
861                antecedentChanged(_editAntecedent.getText().trim());
862                break;
863
864            case "LogicType":       // NOI18N
865                logicTypeChanged(_editOperatorMode.getItemAt(_editOperatorMode.getSelectedIndex()));
866                break;
867
868            case "Variable":       // NOI18N
869                updateVariable();
870                break;
871
872            case "Action":         // NOI18N
873                updateAction();
874                break;
875
876            default:
877                log.warn("Invalid update button press");  // NOI18N
878        }
879        setEditMode(false);
880        _cdlTree.setSelectionPath(_curTreePath);
881        _cdlTree.grabFocus();
882    }
883
884    /**
885     * Change the conditional user name.
886     *
887     * @param newName The proposed new name
888     */
889    void userNameChanged(String newName) {
890        // Check if the User Name has been changed
891        if (!newName.equals(_curConditional.getUserName())) {
892            // user name has changed - check if already in use
893            if (!checkConditionalUserName(newName, _curLogix)) {
894                return;
895            }
896            // user name is unique or blank, change it
897            _curConditional.setUserName(newName);
898            _curNode.setText(buildNodeText("Conditional", _curConditional, 0));  // NOI18N
899            _cdlModel.nodeChanged(_curNode);
900
901            // Update any conditional references
902            ArrayList<String> refList = _conditionalManager.getWhereUsed(_curNodeName);
903            if (refList != null) {
904                for (String ref : refList) {
905                    Conditional cRef = _conditionalManager.getBySystemName(ref);
906                    if (cRef==null){
907                        log.error("Conditional :{}: not found while updating username",ref);
908                        continue;
909                        }
910                    List<ConditionalVariable> varList = cRef.getCopyOfStateVariables();
911                    int idx = 0;
912                    for (ConditionalVariable var : varList) {
913                        // Find the affected conditional variable
914                        if (var.getName().equals(_curNodeName)) {
915                            if (newName.length() > 0) {
916                                var.setGuiName(newName);
917                            } else {
918                                var.setGuiName(_curNodeName);
919                            }
920
921                            // Is the reference (ref) in the same Logix as the target (_curNodeName)
922                            // Skip any cross conditional references
923                            String varLogixName = _conditionalManager.getParentLogix(ref).getSystemName();
924                            String curLogixSName = _curLogix.getSystemName();
925                            if (varLogixName.equals(curLogixSName)) {
926                                // Yes, update the tree node
927                                int cdlCount = _cdlRoot.getChildCount();
928                                for (int j = 0; j < cdlCount; j++) {
929                                    // See if a conditional node contains a reference
930                                    ConditionalTreeNode cdlNode = (ConditionalTreeNode) _cdlRoot.getChildAt(j);
931                                    if (cdlNode.getName().equals(ref)) {
932                                        // The affected variable node will be down 2 levels
933                                        ConditionalTreeNode variables = (ConditionalTreeNode) cdlNode.getChildAt(1);
934                                        ConditionalTreeNode variable = (ConditionalTreeNode) variables.getChildAt(idx);
935                                        variable.setText(buildNodeText("Variable", var, idx));    // NOI18N
936                                        _cdlModel.nodeChanged(variable);
937
938                                    }
939                                }
940                            }
941                        }
942                        idx++;
943                    }
944                    // Commit the changes
945                    cRef.setStateVariables(varList);
946                    // Refresh the local copy in case cRef was a parallel copy
947                    _variableList = _curConditional.getCopyOfStateVariables();
948                }
949            }
950        }
951    }
952
953    /**
954     * Respond to a change of Logic Type in the dialog window by showing/hiding
955     * the _antecedentPanel when Mixed is selected.
956     *
957     * @param newType The selected logic type
958     */
959    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "Except for the root node, all nodes are ConditionalTreeNode")  // NOI18N
960    void logicTypeChanged(Conditional.AntecedentOperator newType) {
961        if (_logicType == newType) {
962            return;
963        }
964
965        makeAntecedent();
966        Operator oper;
967        if (newType != Conditional.AntecedentOperator.MIXED) {
968            oper = Conditional.Operator.OR;
969            if (newType == Conditional.AntecedentOperator.ALL_AND) {
970                oper = Conditional.Operator.AND;
971            }
972
973            // Update the variable list and tree node entries
974            ConditionalTreeNode varHead = (ConditionalTreeNode) _curNode.getPreviousSibling();
975            for (int i = 1; i < _variableList.size(); i++) {
976                ConditionalVariable curVar = _variableList.get(i);
977                curVar.setOpern(oper);
978
979                ConditionalTreeNode varNode = (ConditionalTreeNode) varHead.getChildAt(i);
980                varNode.setText(buildNodeText("Variable", curVar, i));
981                _cdlModel.nodeChanged(varNode);
982            }
983        }
984
985        // update LogicType entry and tree node
986        _curConditional.setLogicType(newType, _antecedent); // non-localized string to store Conditional Antecedent
987        _logicType = newType;
988        _curNode.setText(buildNodeText("LogicType", _curConditional, 0));  // NOI18N
989        _cdlModel.nodeChanged(_curNode);
990
991        // update the variables list
992        _curConditional.setStateVariables(_variableList);
993
994        // Update antecedent node text
995        ConditionalTreeNode parentNode = (ConditionalTreeNode) _curNode.getParent();
996        ConditionalTreeNode antNode = (ConditionalTreeNode) parentNode.getFirstChild();
997        if (antNode.getType().equals("Antecedent")) {  // NOI18N
998            antNode.setText(buildNodeText("Antecedent", _curConditional, 0));  // NOI18N
999            _cdlModel.nodeChanged(antNode);
1000        } else {
1001            log.warn("Unable to find the antecedent node");  // NOI18N
1002        }
1003    }
1004
1005    /**
1006     * Update the antecedent.
1007     *
1008     * @param antecedentText the new antecedent
1009     */
1010    void antecedentChanged(String antecedentText) {
1011        if (validateAntecedent(antecedentText)) {
1012            _antecedent = translateAntecedent(antecedentText, true);
1013            _curConditional.setLogicType(_logicType, _antecedent);
1014            _curNode.setText(buildNodeText("Antecedent", _curConditional, 0));
1015            _cdlModel.nodeChanged(_curNode);
1016        }
1017    }
1018
1019    /**
1020     * Build the antecedent statement.
1021     */
1022    void makeAntecedent() {
1023        _antecedent = makeAntecedent(_variableList);
1024    }
1025
1026    /**
1027     * Add a R# to the antecedent statement.
1028     */
1029    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "Except for the root node, all nodes are ConditionalTreeNode")  // NOI18N
1030    void appendToAntecedent() {
1031        _antecedent = appendToAntecedent(_logicType, _variableList.size(), _antecedent);
1032        _curConditional.setLogicType(_logicType, _antecedent);
1033
1034        // Update antecedent node text
1035        ConditionalTreeNode antNode;
1036        if (_curNodeType.equals("Variables")) {  // NOI18N
1037            antNode = (ConditionalTreeNode) _curNode.getPreviousSibling();
1038        } else {
1039            antNode = (ConditionalTreeNode) ((ConditionalTreeNode) _curNode.getParent()).getPreviousSibling();
1040        }
1041
1042        if (antNode.getType().equals("Antecedent")) {  // NOI18N
1043            antNode.setText(buildNodeText("Antecedent", _curConditional, 0));  // localized display text NOI18N
1044            _cdlModel.nodeChanged(antNode);
1045        } else {
1046            log.warn("Unable to find the antecedent node");  // NOI18N
1047        }
1048    }
1049
1050    /**
1051     * Check the antecedent and logic type.
1052     *
1053     * @param antecedentText The user supplied antecedent text
1054     * @return false if antecedent can't be validated
1055     */
1056    boolean validateAntecedent(String antecedentText) {
1057        return validateAntecedent(_logicType, antecedentText, _variableList, _curConditional);
1058    }
1059
1060    /**
1061     * Update the Actions trigger mode, adjust the Action descriptions.
1062     */
1063    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "Except for the root node, all nodes are ConditionalTreeNode")  // NOI18N
1064    void togglePressed() {
1065        // Toggle the trigger mode
1066        _curLogix.deActivateLogix();
1067        _curConditional.setTriggerOnChange(!_curConditional.getTriggerOnChange());
1068        _triggerMode = _curConditional.getTriggerOnChange();
1069        _curLogix.activateLogix();
1070
1071        // Update node text
1072        _curNode.setText(buildNodeText("TriggerMode", _curConditional, 0));  // NOI18N
1073        _cdlModel.nodeChanged(_curNode);
1074
1075        // refresh the action list to get the updated action descriptions
1076        _actionList = _curConditional.getCopyOfActions();
1077        // get next sibling and update the children node text
1078        ConditionalTreeNode actionsNode = (ConditionalTreeNode) _curNode.getNextSibling();
1079        for (int i = 0; i < _actionList.size(); i++) {
1080            ConditionalAction action = _actionList.get(i);
1081            ConditionalTreeNode actNode = (ConditionalTreeNode) actionsNode.getChildAt(i);
1082            actNode.setText(action.description(_triggerMode));
1083            _cdlModel.nodeChanged(actNode);
1084        }
1085    }
1086
1087    /**
1088     * Refresh the Conditional or Variable state.
1089     */
1090    void checkPressed() {
1091        if (_curNodeType == null || _curNodeType.equals("Conditional")) {
1092            for (int i = 0; i < _cdlRoot.getChildCount(); i++) {
1093                ConditionalTreeNode cdlNode = (ConditionalTreeNode) _cdlRoot.getChildAt(i);
1094                Conditional cdl = _conditionalManager.getBySystemName(cdlNode.getName());
1095                cdlNode.setText(buildNodeText("Conditional", cdl, i));  // NOI18N
1096                _cdlModel.nodeChanged(cdlNode);
1097            }
1098            return;
1099        }
1100
1101        if (_curNodeType.equals("Variables")) {  // NOI18N
1102            for (int i = 0; i < _variableList.size(); i++) {
1103                ConditionalVariable variable = _variableList.get(i);
1104                ConditionalTreeNode varNode = (ConditionalTreeNode) _curNode.getChildAt(i);
1105                varNode.setText(buildNodeText("Variable", variable, i));  // NOI18N
1106                _cdlModel.nodeChanged(varNode);
1107            }
1108        }
1109    }
1110
1111    /**
1112     * Process the node delete request.
1113     */
1114    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "Except for the root node, all nodes are ConditionalTreeNode")  // NOI18N
1115    void deletePressed() {
1116        TreePath parentPath;
1117        ConditionalTreeNode parentNode;
1118        TreeSet<String> oldTargetNames = new TreeSet<>();
1119        TreeSet<String> newTargetNames = new TreeSet<>();
1120        setEditMode(false);
1121
1122        // Conditional
1123        switch (_curNodeType) {
1124            case "Conditional":   // NOI18N
1125                loadReferenceNames(_variableList, oldTargetNames);
1126                // Delete the conditional.
1127                _curLogix.deActivateLogix();
1128                String[] msgs = _curLogix.deleteConditional(_curNodeName);
1129                _curLogix.activateLogix();
1130                if (msgs != null) {
1131                    // Unable to delete due to existing conditional references
1132                   JmriJOptionPane.showMessageDialog(_editLogixFrame,
1133                            Bundle.getMessage("Error11", (Object[]) msgs), // NOI18N
1134                            Bundle.getMessage("ErrorTitle"),
1135                            JmriJOptionPane.ERROR_MESSAGE);  // NOI18N
1136                    return;
1137                }
1138                updateWhereUsed(oldTargetNames, newTargetNames, _curNodeName);
1139                _showReminder = true;
1140
1141                // Update the tree
1142                _cdlRoot.remove(_curNodeRow);
1143                _cdlModel.nodeStructureChanged(_cdlRoot);
1144
1145                // Update the row numbers
1146                int childCount = _cdlRoot.getChildCount();
1147                for (int i = 0; i < childCount; i++) {
1148                    _curNode = (ConditionalTreeNode) _cdlRoot.getChildAt(i);
1149                    _curNode.setRow(i);
1150                    _cdlModel.nodeChanged(_curNode);
1151                }
1152
1153                if (_curLogix.getNumConditionals() < 1 && !_suppressReminder) {
1154                    // warning message - last Conditional deleted
1155                   JmriJOptionPane.showMessageDialog(_editLogixFrame,
1156                            Bundle.getMessage("Warn1"), Bundle.getMessage("WarningTitle"), // NOI18N
1157                            JmriJOptionPane.WARNING_MESSAGE);
1158                }
1159                setMoveButtons();
1160                break;
1161
1162            case "Variable":      // NOI18N
1163                loadReferenceNames(_variableList, oldTargetNames);
1164                if (_variableList.size() < 2 && !_suppressReminder) {
1165                    // warning message - last State Variable deleted
1166                    JmriJOptionPane.showMessageDialog(_editLogixFrame,
1167                            Bundle.getMessage("Warn3"), Bundle.getMessage("WarningTitle"), // NOI18N
1168                            JmriJOptionPane.WARNING_MESSAGE);
1169                }
1170
1171                // Adjust operator
1172                if (_curNodeRow == 0 && _variableList.size() > 1) {
1173                    _variableList.get(1).setOpern(Conditional.Operator.NONE);
1174                }
1175
1176                // Remove the row, update and refresh the Variable list, update references
1177                _variableList.remove(_curNodeRow);
1178                updateVariableList();
1179                loadReferenceNames(_variableList, newTargetNames);
1180                updateWhereUsed(oldTargetNames, newTargetNames, _curNodeName);
1181                _showReminder = true;
1182
1183                // Update the tree components
1184                parentPath = _curTreePath.getParentPath();
1185                parentNode = (ConditionalTreeNode) _curNode.getParent();
1186                parentNode.setRow(_variableList.size());
1187                _cdlModel.nodeChanged(parentNode);
1188
1189                // Update the antecedent
1190                _curNode = (ConditionalTreeNode) parentNode.getPreviousSibling();
1191                antecedentChanged("");
1192
1193                // Update the variable children
1194                parentNode.removeAllChildren();
1195                for (int v = 0; v < _variableList.size(); v++) {
1196                    ConditionalVariable variable = _variableList.get(v);
1197                    _leafNode = new ConditionalTreeNode(buildNodeText("Variable", variable, v), // NOI18N
1198                            "Variable", _curNodeName, v);  // NOI18N
1199                    parentNode.add(_leafNode);
1200                }
1201
1202                _curNode = null;
1203                _newVariableItem = false;
1204                cleanUpVariable();
1205
1206                _cdlModel.nodeStructureChanged(parentNode);
1207                _cdlTree.setSelectionPath(parentPath);
1208                break;
1209
1210            case "Action":        // NOI18N
1211                // Remove the row, update and refresh the Action list
1212                removeActionTimers();
1213                _actionList.remove(_curNodeRow);
1214                updateActionList();
1215                _showReminder = true;
1216
1217                // Update the tree components
1218                parentPath = _curTreePath.getParentPath();
1219                parentNode = (ConditionalTreeNode) _curNode.getParent();
1220                parentNode.setRow(_actionList.size());
1221                _cdlModel.nodeChanged(parentNode);
1222                parentNode.removeAllChildren();
1223                for (int a = 0; a < _actionList.size(); a++) {
1224                    ConditionalAction action = _actionList.get(a);
1225                    _leafNode = new ConditionalTreeNode(buildNodeText("Action", action, a), // NOI18N
1226                            "Action", _curNodeName, a);      // NOI18N
1227                    parentNode.add(_leafNode);
1228                }
1229
1230                _curNode = null;
1231                _newActionItem = false;
1232                cleanUpAction();
1233
1234                _cdlModel.nodeStructureChanged(parentNode);
1235                _cdlTree.setSelectionPath(parentPath);
1236                break;
1237
1238            default:
1239                log.error("Delete called for unsupported node type: '{}'", _curNodeType);  // NOI18N
1240        }
1241    }
1242
1243    /**
1244     * Move a conditional, variable or action row up 1 row.
1245     */
1246    void upPressed() {
1247        _showReminder = true;
1248
1249        switch (_curNodeType) {
1250            case "Conditional":         // NOI18N
1251                // Update Logix index
1252                _curLogix.deActivateLogix();
1253                _curLogix.swapConditional(_curNodeRow - 1, _curNodeRow);
1254                _curLogix.activateLogix();
1255                moveTreeNode("Up");     // NOI18N
1256                break;
1257
1258            case "Variable":            // NOI18N
1259                ConditionalVariable tempVar = _variableList.get(_curNodeRow);
1260                int newVarRow = _curNodeRow - 1;
1261                _variableList.set(_curNodeRow, _variableList.get(newVarRow));
1262                _variableList.set(newVarRow, tempVar);
1263                // Adjust operator
1264                if (newVarRow == 0) {
1265                    _variableList.get(newVarRow).setOpern(Conditional.Operator.NONE);
1266                    Operator newOper = (_logicType == Conditional.AntecedentOperator.ALL_AND)
1267                            ? Conditional.Operator.AND : Conditional.Operator.OR;
1268                    _variableList.get(_curNodeRow).setOpern(newOper);
1269                }
1270                updateVariableList();
1271                moveTreeNode("Up");     // NOI18N
1272                break;
1273
1274            case "Action":              // NOI18N
1275                ConditionalAction tempAct = _actionList.get(_curNodeRow);
1276                int newActRow = _curNodeRow - 1;
1277                _actionList.set(_curNodeRow, _actionList.get(newActRow));
1278                _actionList.set(newActRow, tempAct);
1279                removeActionTimers();
1280                updateActionList();
1281                moveTreeNode("Up");     // NOI18N
1282                break;
1283
1284            default:
1285                log.warn("Move Up called for unsupported node type: '{}'", _curNodeType);  // NOI18N
1286        }
1287    }
1288
1289    /**
1290     * Move a conditional, variable or action row down 1 row.
1291     */
1292    void downPressed() {
1293        _showReminder = true;
1294
1295        switch (_curNodeType) {
1296            case "Conditional":         // NOI18N
1297                _curLogix.deActivateLogix();
1298                _curLogix.swapConditional(_curNodeRow, _curNodeRow + 1);
1299                _curLogix.activateLogix();
1300                moveTreeNode("Down");   // NOI18N
1301                break;
1302
1303            case "Variable":            // NOI18N
1304                ConditionalVariable tempVar = _variableList.get(_curNodeRow);
1305                int newVarRow = _curNodeRow + 1;
1306                _variableList.set(_curNodeRow, _variableList.get(newVarRow));
1307                _variableList.set(newVarRow, tempVar);
1308                // Adjust operator
1309                if (_curNodeRow == 0) {
1310                    _variableList.get(_curNodeRow).setOpern(Conditional.Operator.NONE);
1311                    Operator newOper = (_logicType == Conditional.AntecedentOperator.ALL_AND)
1312                            ? Conditional.Operator.AND : Conditional.Operator.OR;
1313                    _variableList.get(newVarRow).setOpern(newOper);
1314                }
1315                updateVariableList();
1316                moveTreeNode("Down");   // NOI18N
1317                break;
1318
1319            case "Action":              // NOI18N
1320                ConditionalAction tempAct = _actionList.get(_curNodeRow);
1321                int newActRow = _curNodeRow + 1;
1322                _actionList.set(_curNodeRow, _actionList.get(newActRow));
1323                _actionList.set(newActRow, tempAct);
1324                removeActionTimers();
1325                updateActionList();
1326                moveTreeNode("Down");   // NOI18N
1327                break;
1328
1329            default:
1330                log.warn("Move Down called for unsupported node type: '{}'", _curNodeType);  // NOI18N
1331        }
1332    }
1333
1334    /**
1335     * Remove Action timers and listeners before Action list structure changes.
1336     * This relates to moving and deleting rows.  New actions at the end are not problem.
1337     * The issue is that the timer listeners are tied to the action row number.
1338     * This can result in orphan timers and listeners that keep running.
1339     * @since 4.11.2
1340     */
1341    void removeActionTimers() {
1342        // Use the real list, not a copy.
1343        DefaultConditional cdl = (DefaultConditional) _curConditional;
1344        for (ConditionalAction act : cdl.getActionList()) {
1345            if (act.getTimer() != null) {
1346                act.stopTimer();
1347                act.setTimer(null);
1348                act.setListener(null);
1349            }
1350        }
1351    }
1352
1353    /**
1354     * Move a tree node in response to a up or down request.
1355     *
1356     * @param direction The direction of movement, Up or Down
1357     */
1358    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "Except for the root node, all nodes are ConditionalTreeNode")  // NOI18N
1359    void moveTreeNode(String direction) {
1360        // Update the node
1361        int oldRow = _curNodeRow;
1362        if (direction.equals("Up")) {    // NOI18N
1363            _curNodeRow -= 1;
1364        } else {
1365            _curNodeRow += 1;
1366        }
1367        _curNode.setRow(_curNodeRow);
1368        if (_curNodeType.equals("Variable")) {  // NOI18N
1369            _curNode.setText(buildNodeText("Variable", _variableList.get(_curNodeRow), _curNodeRow));   // NOI18N
1370        }
1371        _cdlModel.nodeChanged(_curNode);
1372
1373        // Update the sibling
1374        ConditionalTreeNode siblingNode;
1375        if (direction.equals("Up")) {    // NOI18N
1376            siblingNode = (ConditionalTreeNode) _curNode.getPreviousSibling();
1377            siblingNode.setRow(siblingNode.getRow() + 1);
1378        } else {
1379            siblingNode = (ConditionalTreeNode) _curNode.getNextSibling();
1380            siblingNode.setRow(siblingNode.getRow() - 1);
1381        }
1382        if (_curNodeType.equals("Variable")) {  // NOI18N
1383            siblingNode.setText(buildNodeText("Variable", _variableList.get(oldRow), oldRow));  // NOI18N
1384        }
1385        _cdlModel.nodeChanged(siblingNode);
1386
1387        // Update the tree
1388        if (_curNodeType.equals("Conditional")) {   // NOI18N
1389            _cdlRoot.insert(_curNode, _curNodeRow);
1390            _cdlModel.nodeStructureChanged(_cdlRoot);
1391        } else {
1392            ConditionalTreeNode parentNode = (ConditionalTreeNode) _curNode.getParent();
1393            parentNode.insert(_curNode, _curNodeRow);
1394            _cdlModel.nodeStructureChanged(parentNode);
1395        }
1396        _cdlTree.setSelectionPath(new TreePath(_curNode.getPath()));
1397        setMoveButtons();
1398    }
1399
1400    /**
1401     * Enable/Disable the Up and Down buttons based on the postion in the list.
1402     */
1403    void setMoveButtons() {
1404        if (_curNode == null) {
1405            return;
1406        }
1407
1408        Component[] compList = _moveButtonPanel.getComponents();
1409        JButton up = (JButton) compList[1];
1410        JButton down = (JButton) compList[3];
1411
1412        up.setEnabled(true);
1413        down.setEnabled(true);
1414
1415        int rows;
1416        if (_curNodeType.equals("Conditional")) {       // NOI18N
1417            rows = _curLogix.getNumConditionals();
1418        } else {
1419            ConditionalTreeNode parent = (ConditionalTreeNode) _curNode.getParent();
1420            rows = parent.getRow();
1421        }
1422
1423        if (_curNodeRow < 1) {
1424            up.setEnabled(false);
1425        }
1426        if (_curNodeRow >= rows - 1) {
1427            down.setEnabled(false);
1428        }
1429
1430        // Disable move buttons during Variable or Action add or edit processing, or nothing selected
1431        if ((_newVariableItem && _curNodeType.equals("Variable")) // NOI18N
1432                || (_newActionItem && _curNodeType.equals("Action"))
1433                || (_editActive)
1434                || (_cdlTree.getSelectionCount() == 0)) {  // NOI18N
1435            up.setEnabled(false);
1436            down.setEnabled(false);
1437        }
1438
1439        _moveButtonPanel.setVisible(true);
1440    }
1441
1442    /**
1443     * Respond to Help button press in the Edit Logix menu bar. Only visible when
1444     * using mixed mode and an antecedent node is selected.
1445     */
1446    void helpPressed() {
1447        JmriJOptionPane.showMessageDialog(null,
1448                new String[]{
1449                    Bundle.getMessage("ConditionalHelpText1"), // NOI18N
1450                    Bundle.getMessage("ConditionalHelpText2"), // NOI18N
1451                    Bundle.getMessage("ConditionalHelpText3"), // NOI18N
1452                    Bundle.getMessage("ConditionalHelpText4"), // NOI18N
1453                    Bundle.getMessage("ConditionalHelpText5"), // NOI18N
1454                    Bundle.getMessage("ConditionalHelpText6"), // NOI18N
1455                    Bundle.getMessage("ConditionalHelpText7") // NOI18N
1456                },
1457                Bundle.getMessage("MenuHelp"), JmriJOptionPane.INFORMATION_MESSAGE);  // NOI18N
1458    }
1459
1460    /**
1461     * Cancel the current node edit.
1462     */
1463    void cancelPressed() {
1464        switch (_curNodeType) {
1465            case "Variable":       // NOI18N
1466                cancelEditVariable();
1467                break;
1468
1469            case "Action":         // NOI18N
1470                cancelEditAction();
1471                break;
1472
1473            default:
1474                break;
1475        }
1476        makeDetailGrid("EmptyGrid");  // NOI18N
1477        setEditMode(false);
1478        _cdlTree.setSelectionPath(_curTreePath);
1479        _cdlTree.grabFocus();
1480    }
1481
1482    /**
1483     * Clean up, notify the parent Logix that edit session is done.
1484     */
1485    void donePressed() {
1486        if (_curNodeType != null) {
1487            switch (_curNodeType) {
1488                case "Variable":       // NOI18N
1489                    cancelEditVariable();
1490                    break;
1491
1492                case "Action":         // NOI18N
1493                    cancelEditAction();
1494                    break;
1495
1496                default:
1497                    break;
1498            }
1499        }
1500        closeSinglePanelPickList();
1501        if (_pickTables != null) {
1502            _pickTables.dispose();
1503            _pickTables = null;
1504        }
1505
1506        _editLogixFrame.setVisible(false);
1507        _editLogixFrame.dispose();
1508        _editLogixFrame = null;
1509
1510        logixData.clear();
1511        logixData.put("Finish", _curLogix.getSystemName());  // NOI18N
1512        fireLogixEvent();
1513    }
1514
1515    // ============  Tree Content and Navigation ============
1516
1517    /**
1518     * Create the conditional tree structure using the current Logix.
1519     *
1520     * @return _cdlTree The tree ddefinition with its content
1521     */
1522    JTree buildConditionalTree() {
1523        _cdlRoot = new DefaultMutableTreeNode("Root Node");      // NOI18N
1524        _cdlModel = new DefaultTreeModel(_cdlRoot);
1525        _cdlTree = new JTree(_cdlModel);
1526
1527        createConditionalContent();
1528
1529        // build the tree GUI
1530        _cdlTree.expandPath(new TreePath(_cdlRoot));
1531        _cdlTree.setRootVisible(false);
1532        _cdlTree.setShowsRootHandles(true);
1533        _cdlTree.setScrollsOnExpand(true);
1534        _cdlTree.setExpandsSelectedPaths(true);
1535        _cdlTree.getSelectionModel().setSelectionMode(DefaultTreeSelectionModel.SINGLE_TREE_SELECTION);
1536
1537        // tree listeners
1538        _cdlTree.addTreeSelectionListener(_cdlListener = new TreeSelectionListener() {
1539            @Override
1540            public void valueChanged(TreeSelectionEvent e) {
1541                if (_editActive) {
1542                    if (e.getNewLeadSelectionPath() != _curTreePath) {
1543                        _cdlTree.setSelectionPath(e.getOldLeadSelectionPath());
1544                        showNodeEditMessage();
1545                    }
1546                    return;
1547                }
1548
1549                _curTreePath = _cdlTree.getSelectionPath();
1550                if (_curTreePath != null) {
1551                    Object chkLast = _curTreePath.getLastPathComponent();
1552                    if (chkLast instanceof ConditionalTreeNode) {
1553                        treeRowSelected((ConditionalTreeNode) chkLast);
1554                    }
1555                }
1556            }
1557        });
1558
1559        _cdlTree.addTreeExpansionListener(new TreeExpansionListener() {
1560            @Override
1561            public void treeExpanded(TreeExpansionEvent e) {
1562                ConditionalTreeNode checkNode = (ConditionalTreeNode) e.getPath().getLastPathComponent();
1563                if (checkNode.getType().equals("Variables")) {  // NOI18N
1564                    // Include the field descriptions in the node name
1565                    checkNode.setText(buildNodeText("Variables", _curConditional, 1));  // NOI18N
1566                    _cdlModel.nodeChanged(checkNode);
1567                }
1568            }
1569
1570            @Override
1571            public void treeCollapsed(TreeExpansionEvent e) {
1572                ConditionalTreeNode checkNode = (ConditionalTreeNode) e.getPath().getLastPathComponent();
1573                if (checkNode.getType().equals("Variables")) {  // NOI18N
1574                    // Remove the field descriptions from the node name
1575                    checkNode.setText(buildNodeText("Variables", _curConditional, 0));  // NOI18N
1576                    _cdlModel.nodeChanged(checkNode);
1577                }
1578
1579                if (_cdlTree.getSelectionCount() == 0) {
1580                    makeDetailGrid("EmptyGrid");  // NOI18N
1581                }
1582            }
1583        });
1584
1585        return _cdlTree;
1586    }
1587
1588    /**
1589     * Create the tree content. Level 1 are the conditionals, Level 2 includes the
1590     * antecedent, logic type, trigger mode and parent nodes for Variables and
1591     * Actions, Level 3 contains the detail Variable and Action entries.
1592     */
1593    void createConditionalContent() {
1594        int numConditionals = _curLogix.getNumConditionals();
1595        for (int i = 0; i < numConditionals; i++) {
1596            String csName = _curLogix.getConditionalByNumberOrder(i);
1597            Conditional curConditional = _curLogix.getConditional(csName);
1598            _cdlNode = new ConditionalTreeNode(buildNodeText("Conditional", curConditional, 0), "Conditional", csName, i);    // NOI18N
1599            _cdlRoot.add(_cdlNode);
1600
1601            _leafNode = new ConditionalTreeNode(buildNodeText("Antecedent", curConditional, 0), "Antecedent", csName, 0);   // NOI18N
1602            _cdlNode.add(_leafNode);
1603
1604            _variableList = curConditional.getCopyOfStateVariables();
1605            int varCount = _variableList.size();
1606            _varHead = new ConditionalTreeNode(buildNodeText("Variables", _curConditional, 0), "Variables", csName, varCount);     // NOI18N
1607            _cdlNode.add(_varHead);
1608            for (int v = 0; v < _variableList.size(); v++) {
1609                ConditionalVariable variable = _variableList.get(v);
1610                _leafNode = new ConditionalTreeNode(buildNodeText("Variable", variable, v), "Variable", csName, v);
1611                _varHead.add(_leafNode);
1612            }
1613
1614            _leafNode = new ConditionalTreeNode(buildNodeText("LogicType", curConditional, 0), "LogicType", csName, 0);      // NOI18N
1615            _cdlNode.add(_leafNode);
1616
1617            boolean triggerMode = curConditional.getTriggerOnChange();
1618            _leafNode = new ConditionalTreeNode(buildNodeText("TriggerMode", curConditional, 0), "TriggerMode", csName, 0);      // NOI18N
1619            _cdlNode.add(_leafNode);
1620
1621            _actionList = curConditional.getCopyOfActions();
1622            int actCount = _actionList.size();
1623            _actHead = new ConditionalTreeNode("Actions", "Actions", csName, actCount);      // NOI18N
1624            _cdlNode.add(_actHead);
1625            for (int a = 0; a < _actionList.size(); a++) {
1626                ConditionalAction action = _actionList.get(a);
1627                _leafNode = new ConditionalTreeNode(action.description(triggerMode), "Action", csName, a);      // NOI18N
1628                _actHead.add(_leafNode);
1629            }
1630        }
1631    }
1632
1633    /**
1634     * Change the button row based on the currently selected node type. Invoke
1635     * edit where appropriate.
1636     *
1637     * @param selectedNode The node object
1638     */
1639    void treeRowSelected(ConditionalTreeNode selectedNode) {
1640        // Set the current node variables
1641        _curNode = selectedNode;
1642        _curNodeName = selectedNode.getName();
1643        _curNodeType = selectedNode.getType();
1644        _curNodeText = selectedNode.getText();
1645        _curNodeRow = selectedNode.getRow();
1646
1647        // Set the current conditional variables if different conditional
1648        if (!_curConditionalName.equals(_curNodeName)) {
1649            _curConditional = _conditionalManager.getConditional(_curNodeName);
1650            _antecedent = _curConditional.getAntecedentExpression();
1651            _logicType = _curConditional.getLogicType();
1652            _triggerMode = _curConditional.getTriggerOnChange();
1653            _variableList = _curConditional.getCopyOfStateVariables();
1654            _actionList = _curConditional.getCopyOfActions();
1655            _curConditionalName = _curNodeName;
1656        }
1657
1658        // Reset button bar
1659        _addButtonPanel.setVisible(false);
1660        _checkButtonPanel.setVisible(false);
1661        _toggleButtonPanel.setVisible(false);
1662        _moveButtonPanel.setVisible(false);
1663        _deleteButtonPanel.setVisible(false);
1664        _helpButtonPanel.setVisible(false);
1665
1666        _labelPanel.removeAll();
1667        switch (_curNodeType) {
1668            case "Conditional":     // NOI18N
1669                _labelPanel.add(_conditionalLabel);
1670                _addButtonPanel.setVisible(true);
1671                _checkButtonPanel.setVisible(true);
1672                _deleteButtonPanel.setVisible(true);
1673                setMoveButtons();
1674                editPressed();
1675                break;
1676
1677            case "Antecedent":      // NOI18N
1678                editPressed();
1679                break;
1680
1681            case "LogicType":       // NOI18N
1682                editPressed();
1683                break;
1684
1685            case "TriggerMode":     // NOI18N
1686                _labelPanel.add(_triggerModeLabel);
1687                _toggleButtonPanel.setVisible(true);
1688                makeDetailGrid("EmptyGrid");  // NOI18N
1689                break;
1690
1691            case "Variables":       // NOI18N
1692                _labelPanel.add(_variablesLabel);
1693                _addButtonPanel.setVisible(true);
1694                _checkButtonPanel.setVisible(true);
1695                makeDetailGrid("EmptyGrid");  // NOI18N
1696                break;
1697
1698            case "Variable":        // NOI18N
1699                _labelPanel.add(_variableLabel);
1700                _addButtonPanel.setVisible(true);
1701                _deleteButtonPanel.setVisible(true);
1702                if (_logicType != Conditional.AntecedentOperator.MIXED) {
1703                    setMoveButtons();
1704                }
1705                editPressed();
1706                break;
1707
1708            case "Actions":         // NOI18N
1709                _labelPanel.add(_actionsLabel);
1710                _addButtonPanel.setVisible(true);
1711                makeDetailGrid("EmptyGrid");  // NOI18N
1712                break;
1713
1714            case "Action":          // NOI18N
1715                _labelPanel.add(_actionLabel);
1716                _addButtonPanel.setVisible(true);
1717                _deleteButtonPanel.setVisible(true);
1718                setMoveButtons();
1719                editPressed();
1720                break;
1721
1722            default:
1723                log.warn("Should not be here");  // NOI18N
1724        }
1725    }
1726
1727    /**
1728     * Create the localized node text display strings based on node type.
1729     *
1730     * @param nodeType  The type of the node
1731     * @param component The conditional object or child object
1732     * @param idx       Optional index value
1733     * @return nodeText containing the text to display on the node
1734     */
1735    String buildNodeText(String nodeType, Object component, int idx) {
1736        Conditional cdl;
1737        ConditionalAction act;
1738        ConditionalVariable var;
1739
1740        switch (nodeType) {
1741            case "Conditional":  // NOI18N
1742                cdl = (Conditional) component;
1743                String cdlStatus = (cdl.getState() == Conditional.TRUE)
1744                        ? Bundle.getMessage("True") // NOI18N
1745                        : Bundle.getMessage("False");  // NOI18N
1746                String cdlNames = cdl.getSystemName() + " -- " + cdl.getUserName();
1747                String cdlFill = StringUtils.repeat("&nbsp;", 5);  // NOI18N
1748                String cdlLine = "<html>" + cdlNames + cdlFill + "<strong>[ " + cdlStatus + " ]</strong></html>";  // NOI18N
1749                return cdlLine;
1750
1751            case "Antecedent":  // NOI18N
1752                cdl = (Conditional) component;
1753                String antecedent = translateAntecedent(cdl.getAntecedentExpression(), false);
1754                if (cdl.getLogicType() != Conditional.AntecedentOperator.MIXED) {
1755                    antecedent = "- - - - - - - - -";
1756                }
1757                return Bundle.getMessage("LogixAntecedent") + " " + antecedent;   // NOI18N
1758
1759            case "LogicType":  // NOI18N
1760                cdl = (Conditional) component;
1761                Conditional.AntecedentOperator logicType = cdl.getLogicType();
1762                String logicName; // used for display only
1763                switch (logicType) {
1764                    case ALL_AND:
1765                        logicName = Bundle.getMessage("LogicAND");      // NOI18N
1766                        break;
1767                    case ALL_OR:
1768                        logicName = Bundle.getMessage("LogicOR");       // NOI18N
1769                        break;
1770                    case MIXED:
1771                        logicName = Bundle.getMessage("LogicMixed");    // NOI18N
1772                        break;
1773                    default:
1774                        logicName = "None"; // only used for invalid LogicType
1775                }
1776                return Bundle.getMessage("LabelLogicTypeActions") + "  " + logicName;   // NOI18N
1777
1778            case "TriggerMode":  // NOI18N
1779                cdl = (Conditional) component;
1780                boolean triggerMode = cdl.getTriggerOnChange();
1781                String triggerText;
1782                if (triggerMode) {
1783                    triggerText = Bundle.getMessage("triggerOnChange"); // NOI18N
1784                } else {
1785                    triggerText = Bundle.getMessage("triggerOnAny");    // NOI18N
1786                }
1787                return Bundle.getMessage("LabelTriggerModeActions") + "  " + triggerText; // NOI18N
1788
1789            case "Variables":  // NOI18N
1790                if (idx == 0) {
1791                    // The node is not expanded, return plain content
1792                    return Bundle.getMessage("NodeVariablesCollapsed");  // NOI18N
1793                } else {
1794                    // The node is expanded, include the field names
1795                    return String.format("%s   [[ %s || %s || %s ]]", // NOI18N
1796                            Bundle.getMessage("NodeVariablesExpanded"), // NOI18N
1797                            Bundle.getMessage("ColumnLabelDescription"), // NOI18N
1798                            Bundle.getMessage("ColumnLabelTriggersCalculation"), // NOI18N
1799                            Bundle.getMessage("ColumnState")); // NOI18N
1800                }
1801
1802            case "Variable":  // NOI18N
1803                var = (ConditionalVariable) component;
1804
1805                String rowNum = "R" + (idx + 1) + (idx > 9 ? " " : "  "); // NOI18N
1806                String rowOper = var.getOpernString() + " ";
1807
1808                String rowNot = "";
1809                if (var.isNegated()) {
1810                    rowNot = Bundle.getMessage("LogicNOT") + " ";     // NOI18N
1811                }
1812
1813                String boldFormat = "  || <strong>%s</strong>";  // NOI18N
1814                String rowTrigger = String.format(boldFormat,
1815                        (var.doTriggerActions()) ? Bundle.getMessage("ButtonYes") : Bundle.getMessage("ButtonNo"));  // NOI18N
1816                String rowStatus = String.format(boldFormat,
1817                        (var.evaluate()) ? Bundle.getMessage("True") : Bundle.getMessage("False"));  // NOI18N
1818
1819                String varLine = "<html>" + rowNum + rowOper + rowNot + var.toString() + rowTrigger + rowStatus + "</html>";  // NOI18N
1820                return varLine;
1821
1822            case "Actions":  // NOI18N
1823                return Bundle.getMessage("NodeActions");  // NOI18N
1824
1825            case "Action":   // NOI18N
1826                act = (ConditionalAction) component;
1827                return act.description(_triggerMode);
1828
1829            default:
1830                return "None";
1831        }
1832    }
1833
1834    /**
1835     * Display reminder to save.
1836     */
1837    void showNodeEditMessage() {
1838        if (InstanceManager.getNullableDefault(jmri.UserPreferencesManager.class) != null) {
1839            InstanceManager.getDefault(jmri.UserPreferencesManager.class).
1840                    showInfoMessage(Bundle.getMessage("NodeEditTitle"), // NOI18N
1841                            Bundle.getMessage("NodeEditText"), // NOI18N
1842                            getClassName(),
1843                            "SkipNodeEditMessage"); // NOI18N
1844        }
1845    }
1846
1847    /**
1848     * Focus gained implies intent to make changes, set up for edit.
1849     */
1850    transient FocusListener detailFocusEvent = new FocusListener() {
1851        @Override
1852        public void focusGained(FocusEvent e) {
1853            if (!_editActive) {
1854                setEditMode(true);
1855            }
1856        }
1857
1858        @Override
1859        public void focusLost(FocusEvent e) {
1860        }
1861    };
1862
1863    /**
1864     * Add the focus listener to each detail edit field.
1865     */
1866    void setFocusListeners() {
1867        _editConditionalUserName.addFocusListener(detailFocusEvent);
1868        _editAntecedent.addFocusListener(detailFocusEvent);
1869        _editOperatorMode.addFocusListener(detailFocusEvent);
1870        _variableItemBox.addFocusListener(detailFocusEvent);
1871        _variableOperBox.addFocusListener(detailFocusEvent);
1872        _variableNegated.addFocusListener(detailFocusEvent);
1873        _variableTriggerActions.addFocusListener(detailFocusEvent);
1874        _variableNameField.addFocusListener(detailFocusEvent);
1875        _variableStateBox.addFocusListener(detailFocusEvent);
1876        _variableSignalBox.addFocusListener(detailFocusEvent);
1877        _selectLogixBox.addFocusListener(detailFocusEvent);
1878        _selectConditionalBox.addFocusListener(detailFocusEvent);
1879        _variableCompareOpBox.addFocusListener(detailFocusEvent);
1880        _variableCompareTypeBox.addFocusListener(detailFocusEvent);
1881        _variableData1Field.addFocusListener(detailFocusEvent);
1882        _variableData2Field.addFocusListener(detailFocusEvent);
1883        _actionItemBox.addFocusListener(detailFocusEvent);
1884        _actionNameField.addFocusListener(detailFocusEvent);
1885        _actionTypeBox.addFocusListener(detailFocusEvent);
1886        _actionBox.addFocusListener(detailFocusEvent);
1887        _shortActionString.addFocusListener(detailFocusEvent);
1888        _longActionString.addFocusListener(detailFocusEvent);
1889        _actionSetButton.addFocusListener(detailFocusEvent);
1890        _actionOptionBox.addFocusListener(detailFocusEvent);
1891    }
1892
1893    /**
1894     * Enable/disable buttons based on edit state.
1895     * Open pick lists based on the current SelectionMode.
1896     * The edit state controls the ability to select tree nodes.
1897     *
1898     * @param active True to make edit active, false to make edit inactive
1899     */
1900    void setEditMode(boolean active) {
1901        _editActive = active;
1902        _cancelAction.setEnabled(active);
1903        _updateAction.setEnabled(active);
1904        Component delButton = _deleteButtonPanel.getComponent(0);
1905        if (delButton instanceof JButton) {
1906            delButton.setEnabled(!active);
1907        }
1908        Component addButton = _addButtonPanel.getComponent(0);
1909        if (addButton instanceof JButton) {
1910            addButton.setEnabled(!active);
1911        }
1912        if (_curNodeType != null) {
1913            if (_curNodeType.equals("Conditional") || _curNodeType.equals("Variable") || _curNodeType.equals("Action")) {  // NOI18N
1914                setMoveButtons();
1915            }
1916        }
1917        if (active) {
1918            setPickWindow("Activate", Conditional.ItemType.NONE);  // NOI18N
1919        } else {
1920            setPickWindow("Deactivate", Conditional.ItemType.NONE);  // NOI18N
1921        }
1922    }
1923
1924    /**
1925     * Ceate tabbed Pick Taba;e or Pick Single based on Selection Mode.
1926     * Called by {@link #setEditMode} when edit mode becomes active.
1927     * Called by {@link #variableTypeChanged} and {@link #actionItemChanged} when item type changes.
1928     *
1929     * @param cmd The source or action to be performed.
1930     * @param item The item type for Variable or Action or zero
1931     */
1932    void setPickWindow(String cmd, Conditional.ItemType item) {
1933        if (_selectionMode == SelectionMode.USECOMBO) {
1934            return;
1935        }
1936        // Save the item information
1937        if (cmd.equals("Variable") || cmd.equals("Action")) {  // NOI18N
1938            _pickCommand = cmd;
1939            _pickItem = item;
1940            if (_editActive) {
1941                if (_selectionMode == SelectionMode.USEMULTI) {
1942                    doPickList();
1943                } else {
1944                    doPickSingle();
1945                }
1946            }
1947        }
1948        if (cmd.equals("Activate")) {  // NOI18N
1949            if (_curNodeType.equals("Variable") || _curNodeType.equals("Action")) {  // NOI18N
1950                // Open the appropriate window based on the save values
1951                if (_selectionMode == SelectionMode.USEMULTI) {
1952                    doPickList();
1953                } else {
1954                    doPickSingle();
1955                }
1956            }
1957        }
1958        if (cmd.equals("Deactivate")) {  // NOI18N
1959            // Close/dispose the pick window
1960            hidePickListTable();
1961            closeSinglePanelPickList();
1962        }
1963    }
1964
1965    /**
1966     * Create a Variable or Action based tabbed PickList with appropriate tab selected.
1967     */
1968    void doPickList() {
1969        if (_pickItem == Conditional.ItemType.NONE) {
1970            return;
1971        }
1972        if (_pickTables == null) {
1973            openPickListTable();
1974        }
1975        if (_pickCommand.equals("Variable")) {
1976            setPickListTab(_pickItem, false);
1977        } else if (_pickCommand.equals("Action")) {
1978            setPickListTab(_pickItem, true);
1979        }
1980    }
1981
1982    /**
1983     * Create a Variable or Action based single pane PickList.
1984     */
1985    void doPickSingle() {
1986        if (_pickCommand.equals("Variable")) {
1987            createSinglePanelPickList(_pickItem, new PickSingleListener(_variableNameField, _pickItem), false);
1988        } else if (_pickCommand.equals("Action")) {
1989            createSinglePanelPickList(_pickItem, new PickSingleListener(_actionNameField, _pickItem), true);
1990        }
1991    }
1992
1993    // ============  Edit Variable Section ============
1994
1995    /**
1996     * Called once during class initialization to define the GUI objects. Where
1997     * possible, the combo box content is loaded.
1998     */
1999    void buildVariableComponents() {
2000        // Item Type
2001        _variableItemBox = new JComboBox<>();
2002        for (Conditional.ItemType itemType : Conditional.ItemType.getStateVarList()) {
2003            _variableItemBox.addItem(itemType);
2004        }
2005        JComboBoxUtil.setupComboBoxMaxRows(_variableItemBox);
2006        _variableItemBox.addActionListener(new ActionListener() {
2007            @Override
2008            public void actionPerformed(ActionEvent e) {
2009                Conditional.ItemType newVariableItem = _variableItemBox.getItemAt(_variableItemBox.getSelectedIndex());
2010                if (log.isDebugEnabled()) {
2011                    log.debug("_variableItemBox Listener: new = {}, curr = {}, row = {}",  // NOI18N
2012                            newVariableItem, _curVariableItem, _curNodeRow);
2013                }
2014                if (newVariableItem != _curVariableItem) {
2015                    if (_curNodeRow >= 0) {
2016                        _curVariable = new ConditionalVariable();
2017                        _variableList.set(_curNodeRow, _curVariable);
2018                    }
2019                    _curVariableItem = newVariableItem;
2020                }
2021                variableTypeChanged(newVariableItem);
2022            }
2023        });
2024
2025        // Oper type
2026        _variableOperBox = new JComboBox<String>();
2027        _variableOperBox.addItem(Bundle.getMessage("LogicAND"));  // NOI18N
2028        _variableOperBox.addItem(Bundle.getMessage("LogicOR"));  // NOI18N
2029
2030        // Negation
2031        _variableNegated = new JCheckBox();
2032
2033        // trigger
2034        _variableTriggerActions = new JCheckBox();
2035
2036        // Item Name
2037        _variableNameField = new JTextField(20);
2038
2039        // Combo box section for selecting conditional reference
2040        //   First box selects the Logix, the second selects the conditional within the logix
2041        _selectLogixBox.addItem("XXXXXXXXXXXXXXXXXXXXX");  // NOI18N
2042        _selectConditionalBox.addItem("XXXXXXXXXXXXXXXXXXXXX");  // NOI18N
2043        _selectLogixBox.addActionListener(selectLogixBoxListener);
2044        _selectConditionalBox.addActionListener(selectConditionalBoxListener);
2045
2046        // State Box
2047        _variableStateBox = new JComboBox<>();
2048        _variableStateBox.addItem(Conditional.Type.XXXXXXX);  // NOI18N
2049
2050        // Aspects
2051        _variableSignalBox = new JComboBox<>();
2052        _variableSignalBox.addItem("XXXXXXXXX");  // NOI18N
2053
2054        // Compare operator
2055        _variableCompareOpBox = new JComboBox<>();
2056        for (int i = 1; i <= ConditionalVariable.NUM_COMPARE_OPERATIONS; i++) {
2057            _variableCompareOpBox.addItem(ConditionalVariable.getCompareOperationString(i));
2058        }
2059
2060        // Compare type
2061        _variableCompareTypeBox = new JComboBox<>();
2062        for (Conditional.Type type : Conditional.Type.getMemoryItems()) {
2063            _variableCompareTypeBox.addItem(type);
2064        }
2065        _variableCompareTypeBox.addActionListener(compareTypeBoxListener);
2066
2067        // Data 1
2068        _variableData1Field = new JTextField(10);
2069
2070        // Data 2
2071        _variableData2Field = new JTextField(10);
2072    }
2073
2074    // ------------ Make Variable Edit Grid Panels ------------
2075
2076    /**
2077     * Create a one row grid with just the Variable Type box. This is the base
2078     * for larger grids as well as the initial grid for new State Variables.
2079     *
2080     * @param c the constraints object used for the grid construction
2081     */
2082    void makeEmptyVariableGrid(GridBagConstraints c) {
2083        // Variable type box
2084        c.gridy = 0;
2085        c.gridx = 0;
2086        c.anchor = java.awt.GridBagConstraints.EAST;
2087        JLabel row0Label = new JLabel(Bundle.getMessage("LabelVariableType"));  // NOI18N
2088        row0Label.setToolTipText(Bundle.getMessage("VariableTypeHint"));  // NOI18N
2089        _gridPanel.add(row0Label, c);
2090        c.gridx = 1;
2091        c.anchor = java.awt.GridBagConstraints.WEST;
2092        _gridPanel.add(_variableItemBox, c);
2093    }
2094
2095    /*
2096     * Create the Oper, Not and Trigger rows.
2097     *
2098     * @param c The constraints object used for the grid construction
2099     */
2100    void makeOptionsVariableGrid(GridBagConstraints c) {
2101        makeEmptyVariableGrid(c);
2102
2103        // Oper Select
2104        c.gridy = 1;
2105        c.gridx = 0;
2106        c.anchor = java.awt.GridBagConstraints.EAST;
2107        JLabel row1Label = new JLabel(Bundle.getMessage("ColumnLabelOperator"));  // NOI18N
2108        row1Label.setToolTipText(Bundle.getMessage("VariableOperHint"));  // NOI18N
2109        _gridPanel.add(row1Label, c);
2110        c.gridx = 1;
2111        c.anchor = java.awt.GridBagConstraints.WEST;
2112        _gridPanel.add(_variableOperBox, c);
2113
2114        // Not Select
2115        c.gridy = 2;
2116        c.gridx = 0;
2117        c.anchor = java.awt.GridBagConstraints.EAST;
2118        JLabel row2Label = new JLabel(Bundle.getMessage("ColumnLabelNot"));  // NOI18N
2119        row2Label.setToolTipText(Bundle.getMessage("VariableNotHint"));  // NOI18N
2120        _gridPanel.add(row2Label, c);
2121        c.gridx = 1;
2122        c.anchor = java.awt.GridBagConstraints.WEST;
2123        _gridPanel.add(_variableNegated, c);
2124
2125        // Trigger Select
2126        c.gridy = 3;
2127        c.gridx = 0;
2128        c.anchor = java.awt.GridBagConstraints.EAST;
2129        JLabel row3Label = new JLabel(Bundle.getMessage("ColumnLabelTriggersCalculation"));  // NOI18N
2130        row3Label.setToolTipText(Bundle.getMessage("VariableTriggerHint"));  // NOI18N
2131        _gridPanel.add(row3Label, c);
2132        c.gridx = 1;
2133        c.anchor = java.awt.GridBagConstraints.WEST;
2134        _gridPanel.add(_variableTriggerActions, c);
2135    }
2136
2137    /**
2138     * Create the standard Name and State rows.
2139     * The name field will be either a text field or a combo box.
2140     * The name field label is a variable to support run time changes.
2141     *
2142     * @param c The constraints object used for the grid construction
2143     */
2144    void makeStandardVariableGrid(GridBagConstraints c) {
2145        makeOptionsVariableGrid(c);
2146
2147        // Name Field
2148        c.gridy = 4;
2149        c.gridx = 0;
2150        c.anchor = java.awt.GridBagConstraints.EAST;
2151        _gridPanel.add(_variableNameLabel, c);
2152        c.gridx = 1;
2153        c.anchor = java.awt.GridBagConstraints.WEST;
2154        if (_selectionMode == SelectionMode.USECOMBO) {
2155            _gridPanel.add(_comboNameBox, c);
2156        } else {
2157            _gridPanel.add(_variableNameField, c);
2158        }
2159
2160        // State Box
2161        c.gridy = 5;
2162        c.gridx = 0;
2163        c.anchor = java.awt.GridBagConstraints.EAST;
2164        JLabel row5Label = new JLabel(Bundle.getMessage("LabelVariableState"));  // NOI18N
2165        row5Label.setToolTipText(Bundle.getMessage("VariableStateHint"));  // NOI18N
2166        _gridPanel.add(row5Label, c);
2167        c.gridx = 1;
2168        c.anchor = java.awt.GridBagConstraints.WEST;
2169        _gridPanel.add(_variableStateBox, c);
2170    }
2171
2172    /**
2173     * Add the Aspect field for signal heads and signal masts.
2174     *
2175     * @param c The constraints object used for the grid construction
2176     */
2177    void makeSignalAspectVariableGrid(GridBagConstraints c) {
2178        makeStandardVariableGrid(c);
2179
2180        // Mast Aspect Box
2181        c.gridy = 6;
2182        c.gridx = 0;
2183        c.anchor = java.awt.GridBagConstraints.EAST;
2184        JLabel row5Label = new JLabel(Bundle.getMessage("LabelVariableAspect"));  // NOI18N
2185        row5Label.setToolTipText(Bundle.getMessage("VariableAspectHint"));  // NOI18N
2186        _gridPanel.add(row5Label, c);
2187        c.gridx = 1;
2188        c.anchor = java.awt.GridBagConstraints.WEST;
2189        _gridPanel.add(_variableSignalBox, c);
2190    }
2191
2192    /**
2193     * Create the Logix and Conditional rows and the State row.
2194     *
2195     * @param c The constraints object used for the grid construction
2196     */
2197    void makeConditionalVariableGrid(GridBagConstraints c) {
2198        makeOptionsVariableGrid(c);
2199
2200        // Logix Selection ComboBox
2201        c.gridy = 4;
2202        c.gridx = 0;
2203        c.anchor = java.awt.GridBagConstraints.EAST;
2204        JLabel row4Label = new JLabel(Bundle.getMessage("SelectLogix"));  // NOI18N
2205        row4Label.setToolTipText(Bundle.getMessage("VariableLogixHint"));  // NOI18N
2206        _gridPanel.add(row4Label, c);
2207        c.gridx = 1;
2208        c.anchor = java.awt.GridBagConstraints.WEST;
2209        _gridPanel.add(_selectLogixBox, c);
2210
2211        // Conditional Selection ComboBox
2212        c.gridy = 5;
2213        c.gridx = 0;
2214        c.anchor = java.awt.GridBagConstraints.EAST;
2215        JLabel row5Label = new JLabel(Bundle.getMessage("SelectConditional"));  // NOI18N
2216        row5Label.setToolTipText(Bundle.getMessage("VariableConditionalHint"));  // NOI18N
2217        _gridPanel.add(row5Label, c);
2218        c.gridx = 1;
2219        c.anchor = java.awt.GridBagConstraints.WEST;
2220        _gridPanel.add(_selectConditionalBox, c);
2221
2222        // State Box
2223        c.gridy = 6;
2224        c.gridx = 0;
2225        c.anchor = java.awt.GridBagConstraints.EAST;
2226        JLabel row6Label = new JLabel(Bundle.getMessage("LabelVariableState"));  // NOI18N
2227        row6Label.setToolTipText(Bundle.getMessage("VariableStateHint"));  // NOI18N
2228        _gridPanel.add(row6Label, c);
2229        c.gridx = 1;
2230        c.anchor = java.awt.GridBagConstraints.WEST;
2231        _gridPanel.add(_variableStateBox, c);
2232    }
2233
2234    /**
2235     * Create the Memory specific rows.
2236     *
2237     * @param c The constraints object used for the grid construction
2238     */
2239    void makeMemoryVariableGrid(GridBagConstraints c) {
2240        makeOptionsVariableGrid(c);
2241
2242        // Name Field
2243        c.gridy = 4;
2244        c.gridx = 0;
2245        c.anchor = java.awt.GridBagConstraints.EAST;
2246        JLabel row4Label = new JLabel(Bundle.getMessage("LabelItemName"));  // NOI18N
2247        row4Label.setToolTipText(Bundle.getMessage("NameHintMemory"));  // NOI18N
2248        _gridPanel.add(row4Label, c);
2249        c.gridx = 1;
2250        c.anchor = java.awt.GridBagConstraints.WEST;
2251        if (_selectionMode == SelectionMode.USECOMBO) {
2252            _gridPanel.add(_comboNameBox, c);
2253        } else {
2254            _gridPanel.add(_variableNameField, c);
2255        }
2256
2257        // Comparison Operator
2258        c.gridy = 5;
2259        c.gridx = 0;
2260        c.anchor = java.awt.GridBagConstraints.EAST;
2261        JLabel row5Label = new JLabel(Bundle.getMessage("LabelCompareOp"));  // NOI18N
2262        row5Label.setToolTipText(Bundle.getMessage("CompareHintMemory"));  // NOI18N
2263        _gridPanel.add(row5Label, c);
2264        c.gridx = 1;
2265        c.anchor = java.awt.GridBagConstraints.WEST;
2266        _gridPanel.add(_variableCompareOpBox, c);
2267
2268        // Compare As
2269        c.gridy = 6;
2270        c.gridx = 0;
2271        c.anchor = java.awt.GridBagConstraints.EAST;
2272        JLabel row6Label = new JLabel(Bundle.getMessage("LabelCompareType"));  // NOI18N
2273        row6Label.setToolTipText(Bundle.getMessage("CompareTypeHint"));  // NOI18N
2274        _gridPanel.add(row6Label, c);
2275        c.gridx = 1;
2276        c.anchor = java.awt.GridBagConstraints.WEST;
2277        _gridPanel.add(_variableCompareTypeBox, c);
2278
2279        // Literal Value (default) / Memory Value (name)
2280        c.gridy = 7;
2281        c.gridx = 0;
2282        c.anchor = java.awt.GridBagConstraints.EAST;
2283        _gridPanel.add(_variableMemoryValueLabel, c);
2284        c.gridx = 1;
2285        c.anchor = java.awt.GridBagConstraints.WEST;
2286        _gridPanel.add(_variableData1Field, c);
2287    }
2288
2289    /**
2290     * Create the Fast Clock start and end time rows.
2291     *
2292     * @param c The constraints object used for the grid construction
2293     */
2294    void makeFastClockVariableGrid(GridBagConstraints c) {
2295        makeOptionsVariableGrid(c);
2296
2297        // Start Time Field
2298        c.gridy = 4;
2299        c.gridx = 0;
2300        c.anchor = java.awt.GridBagConstraints.EAST;
2301        JLabel row4Label = new JLabel(Bundle.getMessage("LabelStartTime"));  // NOI18N
2302        row4Label.setToolTipText(Bundle.getMessage("DataHintTime"));  // NOI18N
2303        _gridPanel.add(row4Label, c);
2304        c.gridx = 1;
2305        c.anchor = java.awt.GridBagConstraints.WEST;
2306        _gridPanel.add(_variableData1Field, c);
2307
2308        // End Time Field
2309        c.gridy = 5;
2310        c.gridx = 0;
2311        c.anchor = java.awt.GridBagConstraints.EAST;
2312        JLabel row5Label = new JLabel(Bundle.getMessage("LabelEndTime"));  // NOI18N
2313        row5Label.setToolTipText(Bundle.getMessage("DataHintTime"));  // NOI18N
2314        _gridPanel.add(row5Label, c);
2315        c.gridx = 1;
2316        c.anchor = java.awt.GridBagConstraints.WEST;
2317        _gridPanel.add(_variableData2Field, c);
2318    }
2319
2320    // ------------ Main Variable methods ------------
2321
2322    /**
2323     * Set display to show current state variable (curVariable) parameters.
2324     */
2325    void initializeStateVariables() {
2326        Conditional.Type testType = _curVariable.getType();
2327        if (log.isDebugEnabled()) {
2328            log.debug("initializeStateVariables: testType= {}", testType);  // NOI18N
2329        }
2330        Conditional.ItemType itemType = testType.getItemType();
2331        log.debug("initializeStateVariables: itemType= {}, testType= {}", itemType, testType);  // NOI18N
2332        if (itemType == _variableItemBox.getSelectedItem()) {
2333            // Force a refresh of variableTypeChanged
2334            variableTypeChanged(itemType);
2335        }
2336        _variableItemBox.setSelectedItem(itemType);
2337        _variableOperBox.setSelectedItem(_curVariable.getOpernString());
2338        _variableNegated.setSelected(_curVariable.isNegated());
2339        _variableTriggerActions.setSelected(_curVariable.doTriggerActions());
2340
2341        switch (itemType) {
2342            case NONE:
2343                _variableNameField.setText("");
2344                break;
2345
2346            case SENSOR:
2347            case TURNOUT:
2348            case LIGHT:
2349            case CONDITIONAL:
2350            case WARRANT:
2351                _variableStateBox.setSelectedItem(testType);
2352                _variableNameField.setText(_curVariable.getName());
2353                break;
2354
2355            case SIGNALHEAD:
2356                _variableStateBox.setSelectedItem(testType);
2357                _variableNameField.setText(_curVariable.getName());
2358                if (Conditional.Type.isSignalHeadApperance(testType)) {
2359                    _variableStateBox.setSelectedItem(Conditional.Type.SIGNAL_HEAD_APPEARANCE_EQUALS);
2360                    _variableSignalBox.setSelectedItem(_curVariable.getType());
2361                }
2362                break;
2363
2364            case SIGNALMAST:
2365                // set display to show current state variable (curVariable) parameters
2366                _variableStateBox.setSelectedItem(testType);
2367                _variableNameField.setText(_curVariable.getName());
2368                if (testType == Conditional.Type.SIGNAL_MAST_ASPECT_EQUALS) {
2369                    _variableSignalBox.setSelectedItem(_curVariable.getDataString());
2370                }
2371                break;
2372
2373            case MEMORY:
2374                _variableCompareTypeBox.setSelectedIndex(
2375                        Conditional.Type.getIndexInList(Conditional.Type.getMemoryItems(), testType));
2376                _variableNameField.setText(_curVariable.getName());
2377                int num1 = _curVariable.getNum1() - 1;
2378                if (num1 == -1) {  // former code was only equals
2379                    num1 = ConditionalVariable.EQUAL - 1;
2380                }
2381                _variableCompareOpBox.setSelectedIndex(num1);
2382                _variableData1Field.setText(_curVariable.getDataString());
2383                break;
2384
2385            case CLOCK:
2386                int time = _curVariable.getNum1();
2387                _variableData1Field.setText(formatTime(time / 60, time - ((time / 60) * 60)));
2388                time = _curVariable.getNum2();
2389                _variableData2Field.setText(formatTime(time / 60, time - ((time / 60) * 60)));
2390                _variableNameField.setText("");
2391                break;
2392
2393            case OBLOCK:
2394                _variableNameField.setText(_curVariable.getName());
2395                //_variableStateBox.removeAllItems();
2396                for (Conditional.Type type : Conditional.Type.getOBlockItems()) {
2397                    _variableStateBox.addItem(type);
2398                    if (type.toString().equals(OBlock.getLocalStatusName(_curVariable.getDataString()))) {
2399                        _variableStateBox.setSelectedItem(type);
2400                    }
2401                }
2402//                Iterator<String> names = OBlock.getLocalStatusNames();
2403//                while (names.hasNext()) {
2404//                    _variableStateBox.addItem(names.next());
2405//                }
2406//                _variableStateBox.setSelectedItem(OBlock.getLocalStatusName(_curVariable.getDataString()));
2407                break;
2408
2409            case ENTRYEXIT:
2410                _variableNameField.setText(_curVariable.getBean().getUserName());
2411                _variableStateBox.setSelectedItem(testType);
2412                break;
2413
2414            default:
2415                break;
2416        }
2417        _detailGrid.setVisible(true);
2418    }
2419
2420    /**
2421     * Respond to change in variable type chosen in the State Variable combo box.
2422     *
2423     * @param itemType value representing the newly selected Conditional type,
2424     *                 i.e. ITEM_TYPE_SENSOR
2425     */
2426    private void variableTypeChanged(Conditional.ItemType itemType) {
2427        Conditional.Type testType = _curVariable.getType();
2428        log.debug("variableTypeChanged: itemType= {}, testType= {}", itemType, testType);  // NOI18N
2429        _variableStateBox.removeAllItems();
2430        _variableNameField.removeActionListener(variableSignalHeadNameListener);
2431        _variableNameField.removeActionListener(variableSignalMastNameListener);
2432        _variableStateBox.removeActionListener(variableSignalTestStateListener);
2433        _detailGrid.setVisible(false);
2434
2435        if (_comboNameBox != null) {
2436            for (ActionListener item : _comboNameBox.getActionListeners()) {
2437                _comboNameBox.removeActionListener(item);
2438            }
2439            _comboNameBox.removeFocusListener(detailFocusEvent);
2440        }
2441        setPickWindow("Variable", itemType);  // NOI18N
2442
2443        _variableOperBox.setSelectedItem(_curVariable.getOpernString());
2444        _variableNegated.setSelected(_curVariable.isNegated());
2445        _variableTriggerActions.setSelected(_curVariable.doTriggerActions());
2446
2447        switch (itemType) {
2448            case NONE:
2449                makeDetailGrid("EmptyVariable");  // NOI18N
2450                break;
2451
2452            case SENSOR:
2453                _variableNameLabel.setToolTipText(Bundle.getMessage("NameHintSensor"));  // NOI18N
2454                for (Conditional.Type type : Conditional.Type.getSensorItems()) {
2455                    _variableStateBox.addItem(type);
2456                }
2457                setVariableNameBox(itemType);
2458                makeDetailGrid("StandardVariable");  // NOI18N
2459                break;
2460
2461            case TURNOUT:
2462                _variableNameLabel.setToolTipText(Bundle.getMessage("NameHintTurnout"));  // NOI18N
2463                for (Conditional.Type type : Conditional.Type.getTurnoutItems()) {
2464                    _variableStateBox.addItem(type);
2465                }
2466                setVariableNameBox(itemType);
2467                makeDetailGrid("StandardVariable");  // NOI18N
2468                break;
2469
2470            case LIGHT:
2471                _variableNameLabel.setToolTipText(Bundle.getMessage("NameHintLight"));  // NOI18N
2472                for (Conditional.Type type : Conditional.Type.getLightItems()) {
2473                    _variableStateBox.addItem(type);
2474                }
2475                setVariableNameBox(itemType);
2476                makeDetailGrid("StandardVariable");  // NOI18N
2477                break;
2478
2479            case SIGNALHEAD:
2480                _variableNameLabel.setToolTipText(Bundle.getMessage("NameHintSignal"));  // NOI18N
2481                loadJComboBoxWithHeadAppearances(_variableSignalBox, _variableNameField.getText().trim());
2482
2483                for (Conditional.Type type : Conditional.Type.getSignalHeadStateMachineItems()) {
2484                    _variableStateBox.addItem(type);
2485                }
2486
2487                setVariableNameBox(itemType);
2488                if (testType == Conditional.Type.SIGNAL_HEAD_APPEARANCE_EQUALS) {
2489                    makeDetailGrid("SignalAspectVariable");  // NOI18N
2490                } else {
2491                    makeDetailGrid("StandardVariable");  // NOI18N
2492                }
2493
2494                _variableNameField.addActionListener(variableSignalHeadNameListener);
2495                _variableStateBox.addActionListener(variableSignalTestStateListener);
2496                break;
2497
2498            case SIGNALMAST:
2499                _variableNameLabel.setToolTipText(Bundle.getMessage("NameHintSignalMast"));  // NOI18N
2500                _variableNameField.addActionListener(variableSignalMastNameListener);
2501                _variableStateBox.addActionListener(variableSignalTestStateListener);
2502                loadJComboBoxWithMastAspects(_variableSignalBox, _variableNameField.getText().trim());
2503
2504                for (Conditional.Type type : Conditional.Type.getSignalMastItems()) {
2505                    _variableStateBox.addItem(type);
2506                }
2507                setVariableNameBox(itemType);
2508                if (testType == Conditional.Type.SIGNAL_MAST_ASPECT_EQUALS) {
2509                    makeDetailGrid("SignalAspectVariable");  // NOI18N
2510                } else {
2511                    makeDetailGrid("StandardVariable");  // NOI18N
2512                }
2513                break;
2514
2515            case MEMORY:
2516                _variableNameLabel.setToolTipText(Bundle.getMessage("NameHintMemory"));  // NOI18N
2517                setVariableNameBox(itemType);
2518                makeDetailGrid("MemoryVariable");  // NOI18N
2519                compareTypeChanged(testType);   // Force the label update
2520                break;
2521
2522            case CONDITIONAL:
2523                for (Conditional.Type type : Conditional.Type.getConditionalItems()) {
2524                    _variableStateBox.addItem(type);
2525                }
2526                loadSelectLogixBox();
2527                makeDetailGrid("ConditionalVariable");  // NOI18N
2528                _selectLogixBox.addActionListener(selectLogixBoxListener);
2529                _selectConditionalBox.addActionListener(selectConditionalBoxListener);
2530                break;
2531
2532            case WARRANT:
2533                _variableNameLabel.setToolTipText(Bundle.getMessage("NameHintWarrant"));  // NOI18N
2534                for (Conditional.Type type : Conditional.Type.getWarrantItems()) {
2535                    _variableStateBox.addItem(type);
2536                }
2537                setVariableNameBox(itemType);
2538                makeDetailGrid("StandardVariable");  // NOI18N
2539                break;
2540
2541            case CLOCK:
2542                makeDetailGrid("FastClockVariable");  // NOI18N
2543                break;
2544
2545            case OBLOCK:
2546                _variableNameLabel.setToolTipText(Bundle.getMessage("NameHintOBlock"));  // NOI18N
2547                _variableStateBox.removeAllItems();
2548                for (Conditional.Type type : Conditional.Type.getOBlockItems()) {
2549                    _variableStateBox.addItem(type);
2550                }
2551//                Iterator<String> names = OBlock.getLocalStatusNames();
2552//                while (names.hasNext()) {
2553//                    _variableStateBox.addItem(names.next());
2554//                }
2555                setVariableNameBox(itemType);
2556                makeDetailGrid("StandardVariable");  // NOI18N
2557                break;
2558
2559            case ENTRYEXIT:
2560                _variableNameLabel.setToolTipText(Bundle.getMessage("NameHintEntryExit"));  // NOI18N
2561                _variableNameField.setText(_curVariable.getName());
2562                for (Conditional.Type type : Conditional.Type.getEntryExitItems()) {
2563                    _variableStateBox.addItem(type);
2564                }
2565                setVariableNameBox(itemType);
2566                makeDetailGrid("StandardVariable");  // NOI18N
2567                break;
2568
2569            default:
2570                break;
2571        }
2572    }
2573
2574    /**
2575     * Update the name combo box selection based on the current contents of the
2576     * name field. Called by variableItemChanged.
2577     *
2578     * @since 4.7.3
2579     * @param itemType The item type, such as sensor or turnout.
2580     */
2581    void setVariableNameBox(Conditional.ItemType itemType) {
2582        if (_selectionMode != SelectionMode.USECOMBO) {
2583            return;
2584        }
2585        _comboNameBox = createNameBox(itemType);
2586        if (_comboNameBox == null) {
2587            return;
2588        }
2589        // Select the current entry, add the listener
2590        _comboNameBox.setSelectedItemByName(_curVariable.getName());
2591        _comboNameBox.addActionListener(new NameBoxListener(_variableNameField));
2592        _comboNameBox.addFocusListener(detailFocusEvent);
2593    }
2594
2595    // ------------ Variable detail methods ------------
2596
2597    /**
2598     * Respond to Cancel variable button
2599     */
2600    void cancelEditVariable() {
2601        if (_newVariableItem) {
2602            _newVariableItem = false;
2603            deletePressed();
2604        }
2605        cleanUpVariable();
2606    }
2607
2608    /**
2609     * Respond to Update Variable button in the Edit Variable pane.
2610     */
2611    void updateVariable() {
2612        if (!validateVariable()) {
2613            return;
2614        }
2615        _newVariableItem = false;
2616        _curConditional.setStateVariables(_variableList);
2617
2618        // Update conditional references
2619        TreeSet<String> newTargetNames = new TreeSet<>();
2620        loadReferenceNames(_variableList, newTargetNames);
2621        updateWhereUsed(_oldTargetNames, newTargetNames, _curNodeName);
2622
2623        // Update the tree nodeChanged
2624        _curNode.setText(buildNodeText("Variable", _curVariable, _curNodeRow));  // NOI18N
2625        _cdlModel.nodeChanged(_curNode);
2626        cleanUpVariable();
2627    }
2628
2629    /**
2630     * Clean up: Cancel, Update and Delete Variable buttons.
2631     */
2632    void cleanUpVariable() {
2633        if (_logicType != Conditional.AntecedentOperator.MIXED) {
2634            setMoveButtons();
2635        }
2636    }
2637
2638    /**
2639     * Load the Logix selection box. Set the selection to the current Logix.
2640     *
2641     * @since 4.7.4
2642     */
2643    void loadSelectLogixBox() {
2644        // Get the current Logix name for selecting the current combo box row
2645        String cdlName = _curVariable.getName();
2646        String lgxName;
2647        if (cdlName.length() == 0 || (_curVariable.getType() != Conditional.Type.CONDITIONAL_TRUE
2648                && _curVariable.getType() != Conditional.Type.CONDITIONAL_FALSE)) {
2649            // Use the current logix name for "add" state variable
2650            lgxName = _curLogix.getSystemName();
2651        } else {
2652            Logix x = _conditionalManager.getParentLogix(cdlName);
2653            if (x == null) {
2654                log.error("Unable to find the Logix for {}, using the current Logix", cdlName);  // NOI18N
2655                lgxName = _curLogix.getSystemName();
2656            } else {
2657                lgxName = x.getSystemName();
2658            }
2659        }
2660
2661        _selectLogixBox.removeAllItems();
2662        _selectLogixMap.clear();
2663
2664        String itemKey = "";
2665        for (Logix lgx : _logixManager.getNamedBeanSet()) {
2666            String sName = lgx.getSystemName();
2667            if (sName.equals("SYS")) {  // NOI18N
2668                // Cannot refer to sensor name groups
2669                continue;
2670            }
2671            String uName = lgx.getUserName();
2672            String itemName = "";
2673            if (uName == null || uName.length() < 1) {
2674                itemName = sName;
2675            } else {
2676                itemName = uName + " ( " + sName + " )";
2677            }
2678            _selectLogixMap.put(itemName, sName);
2679            if (lgxName.equals(sName)) {
2680                itemKey = itemName;
2681            }
2682        }
2683
2684        // Load the combo box
2685        for (String item : _selectLogixMap.keySet()) {
2686            _selectLogixBox.addItem(item);
2687        }
2688
2689        JComboBoxUtil.setupComboBoxMaxRows(_selectLogixBox);
2690        _selectLogixBox.setSelectedItem(itemKey);
2691        loadSelectConditionalBox(lgxName);
2692    }
2693
2694    /**
2695     * Load the Conditional selection box. The first row is a prompt.
2696     *
2697     * @since 4.7.4
2698     * @param logixName The Logix system name for selecting the owned
2699     *                  Conditionals
2700     */
2701    void loadSelectConditionalBox(String logixName) {
2702        // Get the current Conditional name for selecting the current combo box row
2703        String cdlName = _curVariable.getName();
2704
2705        _selectConditionalBox.removeAllItems();
2706        _selectConditionalList.clear();
2707
2708        // Create the first row
2709        String itemKey = Bundle.getMessage("SelectFirstRow");  // NOI18N
2710        _selectConditionalBox.addItem(itemKey);
2711        _selectConditionalList.add("-None-");  // NOI18N
2712
2713        Logix x = _logixManager.getBySystemName(logixName);
2714        if (x == null) {
2715            log.error("Logix '{}' not found while building the conditional list", logixName);  // NOI18N
2716            return;
2717        }
2718        if (x.getNumConditionals() == 0) {
2719            return;
2720        }
2721        for (String cName : _conditionalManager.getSystemNameListForLogix(x)) {
2722            Conditional c = _conditionalManager.getConditional(cName);
2723            if (_curConditional.getSystemName().equals(c.getSystemName())) {
2724                // Don't add myself to the list
2725                continue;
2726            }
2727            String uName = c.getUserName();
2728            String itemName = "";
2729            if (uName == null || uName.length() < 1) {
2730                itemName = cName;
2731            } else {
2732                itemName = uName + " ( " + cName + " )";
2733            }
2734            _selectConditionalBox.addItem(itemName);
2735            _selectConditionalList.add(cName);
2736            if (cdlName.equals(cName)) {
2737                itemKey = itemName;
2738            }
2739        }
2740        JComboBoxUtil.setupComboBoxMaxRows(_selectConditionalBox);
2741        _selectConditionalBox.setSelectedItem(itemKey);
2742    }
2743
2744    /**
2745     * Check if Memory type in a Conditional was changed by the user.
2746     * <p>
2747     * Update GUI if it has.
2748     *
2749     * @param testType One of the four types
2750     */
2751    private void compareTypeChanged(Conditional.Type testType) {
2752        if ((testType == Conditional.Type.MEMORY_COMPARE)
2753                || (testType == Conditional.Type.MEMORY_COMPARE_INSENSITIVE)) {
2754            _variableMemoryValueLabel.setText(Bundle.getMessage("LabelMemoryValue"));  // NOI18N
2755            _variableMemoryValueLabel.setToolTipText(Bundle.getMessage("DataHintMemory"));  // NOI18N
2756        } else {
2757            _variableMemoryValueLabel.setText(Bundle.getMessage("LabelLiteralValue"));  // NOI18N
2758            _variableMemoryValueLabel.setToolTipText(Bundle.getMessage("DataHintValue"));  // NOI18N
2759        }
2760    }
2761
2762    /**
2763     * Fetch valid localized appearances for a given Signal Head.
2764     * <p>
2765     * Warn if head is not found.
2766     *
2767     * @param box            the comboBox on the setup pane to fill
2768     * @param signalHeadName user or system name of the Signal Head
2769     */
2770    void loadJComboBoxWithHeadAppearances(JComboBox<String> box, String signalHeadName) {
2771        box.removeAllItems();
2772        log.debug("loadJComboBoxWithSignalHeadAppearances called with name: {}", signalHeadName);  // NOI18N
2773        SignalHead h = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHeadName);
2774        if (h == null) {
2775            box.addItem(Bundle.getMessage("PromptLoadHeadName"));  // NOI18N
2776        } else {
2777            String[] v = h.getValidStateNames();
2778            for (int i = 0; i < v.length; i++) {
2779                box.addItem(v[i]);
2780            }
2781            box.setSelectedItem(h.getAppearanceName());
2782        }
2783    }
2784
2785    /**
2786     * Fetch valid aspects for a given Signal Mast.
2787     * <p>
2788     * Warn if mast is not found.
2789     *
2790     * @param box      the comboBox on the setup pane to fill
2791     * @param mastName user or system name of the Signal Mast
2792     */
2793    void loadJComboBoxWithMastAspects(JComboBox<String> box, String mastName) {
2794        box.removeAllItems();
2795        SignalMast m = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(mastName);
2796        if (m == null) {
2797            box.addItem(Bundle.getMessage("PromptLoadMastName"));  // NOI18N
2798        } else {
2799            java.util.Vector<String> v = m.getValidAspects();
2800            for (int i = 0; i < v.size(); i++) {
2801                box.addItem(v.get(i));
2802            }
2803            box.setSelectedItem(m.getAspect());
2804        }
2805    }
2806
2807    // ------------ Variable update processes ------------
2808
2809    /**
2810     * Validate Variable data from Edit Variable panel, and transfer it to
2811     * current variable object as appropriate.
2812     * <p>
2813     * Messages are sent to the user for any errors found. This routine returns
2814     * false immediately after finding the first error, even if there might be
2815     * more errors.
2816     *
2817     * @return true if all data checks out OK, otherwise false
2818     */
2819    boolean validateVariable() {
2820        String name = _variableNameField.getText().trim();
2821        _variableNameField.setText(name);
2822        _curVariable.setDataString("");
2823        _curVariable.setNum1(0);
2824        _curVariable.setNum2(0);
2825
2826        updateVariableOperator();
2827        updateVariableNegation();
2828        _curVariable.setTriggerActions(_variableTriggerActions.isSelected());
2829
2830        Conditional.ItemType itemType = _variableItemBox.getItemAt(_variableItemBox.getSelectedIndex());
2831        if (!checkIsAction(name, itemType) ) {
2832            return false;
2833        }
2834        Conditional.Type testType = Conditional.Type.NONE;
2835        switch (itemType) {
2836            case SENSOR:
2837            case TURNOUT:
2838            case LIGHT:
2839            case SIGNALHEAD:
2840            case SIGNALMAST:
2841            case CONDITIONAL:
2842            case WARRANT:
2843            case ENTRYEXIT:
2844                testType = _variableStateBox.getItemAt(_variableStateBox.getSelectedIndex());
2845                break;
2846            case MEMORY:
2847                testType = _variableCompareTypeBox.getItemAt(_variableCompareTypeBox.getSelectedIndex());
2848                break;
2849            case CLOCK:
2850                testType = Conditional.Type.FAST_CLOCK_RANGE;
2851                break;
2852            case OBLOCK:
2853                testType = Conditional.Type.BLOCK_STATUS_EQUALS;
2854                break;
2855            default:
2856                JmriJOptionPane.showMessageDialog(_editLogixFrame,
2857                        Bundle.getMessage("ErrorVariableType"), Bundle.getMessage("ErrorTitle"), // NOI18N
2858                        JmriJOptionPane.ERROR_MESSAGE);
2859                return false;
2860        }
2861        _curVariable.setType(testType);
2862        log.debug("validateVariable: itemType= {}, testType= {}", itemType, testType);  // NOI18N
2863        switch (itemType) {
2864            case SENSOR:
2865                name = validateSensorReference(name);
2866                if (name == null) {
2867                    return false;
2868                }
2869                break;
2870            case TURNOUT:
2871                name = validateTurnoutReference(name);
2872                if (name == null) {
2873                    return false;
2874                }
2875                break;
2876            case CONDITIONAL:
2877                name = validateConditionalReference(name);
2878                if (name == null) {
2879                    return false;
2880                }
2881                _curVariable.setName(name);
2882                Conditional c = _conditionalManager.getBySystemName(name);
2883                if (c == null) {
2884                    return false;
2885                }
2886                String uName = c.getUserName();
2887                if (uName == null || uName.isEmpty()) {
2888                    _curVariable.setGuiName(c.getSystemName());
2889                } else {
2890                    _curVariable.setGuiName(uName);
2891                }
2892                break;
2893            case LIGHT:
2894                name = validateLightReference(name);
2895                if (name == null) {
2896                    return false;
2897                }
2898                break;
2899            case MEMORY:
2900                name = validateMemoryReference(name);
2901                if (name == null) {
2902                    return false;
2903                }
2904                String name2 = _variableData1Field.getText();
2905                if ((testType == Conditional.Type.MEMORY_COMPARE)
2906                        || (testType == Conditional.Type.MEMORY_COMPARE_INSENSITIVE)) {
2907                    name2 = validateMemoryReference(name2);
2908                    if (name2 == null) {
2909                        return false;
2910                    }
2911                }
2912                _curVariable.setDataString(name2);
2913                _curVariable.setNum1(_variableCompareOpBox.getSelectedIndex() + 1);
2914                break;
2915            case CLOCK:
2916                int beginTime = parseTime(_variableData1Field.getText());
2917                if (beginTime < 0) {
2918                    // parse error occurred - message has been sent
2919                    return (false);
2920                }
2921                int endTime = parseTime(_variableData2Field.getText());
2922                if (endTime < 0) {
2923                    return (false);
2924                }
2925                // set beginning and end time (minutes since midnight)
2926                _curVariable.setNum1(beginTime);
2927                _curVariable.setNum2(endTime);
2928                name = "Clock";  // NOI18N
2929                break;
2930            case SIGNALHEAD:
2931                name = validateSignalHeadReference(name);
2932                if (name == null) {
2933                    return false;
2934                }
2935                if (testType == Conditional.Type.SIGNAL_HEAD_APPEARANCE_EQUALS) {
2936                    String appStr = (String) _variableSignalBox.getSelectedItem();
2937                    if (appStr != null) {
2938                        Conditional.Type type = ConditionalVariable.stringToVariableTest(appStr);
2939                        if (type == Conditional.Type.ERROR) {
2940                           JmriJOptionPane.showMessageDialog(_editLogixFrame, Bundle.getMessage("ErrorAppearance"), Bundle.getMessage("ErrorTitle"), // NOI18N
2941                                    JmriJOptionPane.ERROR_MESSAGE);
2942                            return false;
2943                        }
2944                        _curVariable.setType(type);
2945                        _curVariable.setDataString(appStr);
2946                        log.debug("SignalHead \"{}\"of type '{}' _variableSignalBox.getSelectedItem()= {}", name, testType, _variableSignalBox.getSelectedItem()); // NOI18N
2947                    } else {
2948                        log.warn("null selection in _variableSignalBox");
2949                    }
2950                }
2951                break;
2952            case SIGNALMAST:
2953                name = validateSignalMastReference(name);
2954                if (name == null) {
2955                    return false;
2956                }
2957                if (testType == Conditional.Type.SIGNAL_MAST_ASPECT_EQUALS) {
2958                    if (_variableSignalBox.getSelectedIndex() < 0) {
2959                       JmriJOptionPane.showMessageDialog(_editLogixFrame,
2960                                Bundle.getMessage("ErrorAspect"), Bundle.getMessage("ErrorTitle"), // NOI18N
2961                                JmriJOptionPane.ERROR_MESSAGE);
2962                        return false;
2963                    }
2964                    // save the selected aspect for comparison
2965                    _curVariable.setDataString((String) _variableSignalBox.getSelectedItem());
2966                    //                _curVariable.setType(ConditionalVariable.stringToVariableTest(appStr));
2967                }
2968                break;
2969            case WARRANT:
2970                name = validateWarrantReference(name);
2971                if (name == null) {
2972                    return false;
2973                }
2974                break;
2975            case OBLOCK:
2976                name = validateOBlockReference(name);
2977                if (name == null) {
2978                    return false;
2979                }
2980                String stri18n = (String) _variableStateBox.getSelectedItem();
2981                if (stri18n != null) {
2982                    _curVariable.setDataString(OBlock.getSystemStatusName(stri18n));
2983                    log.debug("OBlock \"{}\"of type '{}' _variableSignalBox.getSelectedItem()= {}", name, testType, _variableSignalBox.getSelectedItem()); // NOI18N
2984                }
2985                break;
2986            case ENTRYEXIT:
2987                name = validateEntryExitReference(name);
2988                if (name == null) {
2989                    return false;
2990                }
2991                break;
2992            default:
2993               JmriJOptionPane.showMessageDialog(_editLogixFrame,
2994                        Bundle.getMessage("ErrorVariableType"), Bundle.getMessage("ErrorTitle"), // NOI18N
2995                        JmriJOptionPane.ERROR_MESSAGE);
2996                return false;
2997        }
2998        _curVariable.setName(name);
2999        boolean result = _curVariable.evaluate();
3000        log.debug("State Variable \"{}\" of type '{}' state= {} type= {}",
3001                name, testType.getTestTypeString(),
3002                result, _curVariable.getType());  // NOI18N
3003        if (_curVariable.getType() == Conditional.Type.NONE) {
3004           JmriJOptionPane.showMessageDialog(_editLogixFrame,
3005                    Bundle.getMessage("ErrorVariableState"), Bundle.getMessage("ErrorTitle"), // NOI18N
3006                    JmriJOptionPane.ERROR_MESSAGE);
3007            return false;
3008        }
3009        return (true);
3010    }
3011
3012    /**
3013     * Update the variable operation. If a change has occurred, also update the
3014     * antecedent and antecedent tree node.
3015     */
3016    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "Except for the root node, all nodes are ConditionalTreeNode")  // NOI18N
3017    void updateVariableOperator() {
3018        Operator oldOper = _curVariable.getOpern();
3019        if (_curNodeRow > 0) {
3020            if (_variableOperBox.getSelectedIndex() == 0) {
3021                _curVariable.setOpern(Conditional.Operator.AND);
3022            } else {
3023                _curVariable.setOpern(Conditional.Operator.OR);
3024            }
3025        } else {
3026            _curVariable.setOpern(Conditional.Operator.NONE);
3027        }
3028        if (_curVariable.getOpern() != oldOper) {
3029            makeAntecedent();
3030            _curConditional.setLogicType(_logicType, _antecedent); // non-localized
3031            ConditionalTreeNode antLeaf = (ConditionalTreeNode) ((ConditionalTreeNode) _curNode.getParent()).getPreviousSibling();
3032            antLeaf.setText(buildNodeText("Antecedent", _curConditional, 0));  // NOI18N
3033            _cdlModel.nodeChanged(antLeaf);
3034        }
3035    }
3036
3037    /**
3038     * Update the variable negation. If a change has occurred, also update the
3039     * antecedent and antecedent tree node.
3040     */
3041    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "Except for the root node, all nodes are ConditionalTreeNode")  // NOI18N
3042    void updateVariableNegation() {
3043        boolean state = _curVariable.isNegated();
3044        if (_variableNegated.isSelected()) {
3045            _curVariable.setNegation(true);
3046        } else {
3047            _curVariable.setNegation(false);
3048        }
3049        if (_curVariable.isNegated() != state) {
3050            makeAntecedent();
3051            _curConditional.setLogicType(_logicType, _antecedent); // non-localized
3052            ConditionalTreeNode antLeaf = (ConditionalTreeNode) ((ConditionalTreeNode) _curNode.getParent()).getPreviousSibling();
3053            antLeaf.setText(buildNodeText("Antecedent", _curConditional, 0));
3054            _cdlModel.nodeChanged(antLeaf);
3055        }
3056    }
3057
3058    /**
3059     * Update the conditional variable list and refresh the local copy.
3060     * The parent Logix is de-activated and re-activated. This ensures
3061     * that listeners are properly handled.
3062     * @since 4.11.2
3063     */
3064    void updateVariableList() {
3065        _curLogix.deActivateLogix();
3066        _curConditional.setStateVariables(_variableList);
3067        _variableList = _curConditional.getCopyOfStateVariables();
3068        _curLogix.activateLogix();
3069    }
3070
3071    // ------------ Variable detail listeners ------------
3072
3073    transient ActionListener variableSignalHeadNameListener = new ActionListener() {
3074        @Override
3075        public void actionPerformed(ActionEvent e) {
3076            // fired when signal head name changes, but only
3077            // while in signal head mode
3078            log.debug("variableSignalHeadNameListener fires; _variableNameField : {}", _variableNameField.getText().trim());  // NOI18N
3079            loadJComboBoxWithHeadAppearances(_variableSignalBox, _variableNameField.getText().trim());
3080        }
3081    };
3082
3083    transient ActionListener variableSignalMastNameListener = new ActionListener() {
3084        @Override
3085        public void actionPerformed(ActionEvent e) {
3086            // fired when signal mast name changes, but only
3087            // while in signal mast mode
3088            log.debug("variableSignalMastNameListener fires; _variableNameField : {}", _variableNameField.getText().trim());  // NOI18N
3089            loadJComboBoxWithMastAspects(_variableSignalBox, _variableNameField.getText().trim());
3090        }
3091    };
3092
3093    transient ActionListener variableSignalTestStateListener = new ActionListener() {
3094        @Override
3095        public void actionPerformed(ActionEvent e) {
3096            log.debug("variableSignalTestStateListener fires; _variableItemBox.getSelectedIndex()= \"{}\" _variableStateBox.getSelectedIndex()= \"{}\"", // NOI18N
3097                    _variableItemBox.getSelectedIndex(),
3098                    _variableStateBox.getSelectedIndex());
3099
3100            Conditional.ItemType itemType = _variableItemBox.getItemAt(_variableItemBox.getSelectedIndex());
3101
3102            if (_variableStateBox.getSelectedIndex() == 1) {
3103                if (itemType == Conditional.ItemType.SIGNALHEAD) {
3104                    loadJComboBoxWithHeadAppearances(_variableSignalBox, _variableNameField.getText().trim());
3105                    _detailGrid.setVisible(false);
3106                    makeDetailGrid("SignalAspectVariable");  // NOI18N
3107                } else if (itemType == Conditional.ItemType.SIGNALMAST) {
3108                    loadJComboBoxWithMastAspects(_variableSignalBox, _variableNameField.getText().trim());
3109                    _detailGrid.setVisible(false);
3110                    makeDetailGrid("SignalAspectVariable");  // NOI18N
3111                } else {
3112                    _detailGrid.setVisible(false);
3113                    makeDetailGrid("StandardVariable");  // NOI18N
3114                }
3115            } else {
3116                _detailGrid.setVisible(false);
3117                makeDetailGrid("StandardVariable");  // NOI18N
3118            }
3119        }
3120    };
3121
3122    transient ActionListener selectLogixBoxListener = new ActionListener() {
3123        @Override
3124        public void actionPerformed(ActionEvent e) {
3125            String lgxItem = (String) _selectLogixBox.getSelectedItem();
3126            if (lgxItem != null) {
3127                String lgxName = _selectLogixMap.get(lgxItem);
3128                if (lgxName != null) {
3129                    loadSelectConditionalBox(lgxName);
3130                }
3131            }
3132        }
3133    };
3134
3135    transient ActionListener selectConditionalBoxListener = new ActionListener() {
3136        @Override
3137        public void actionPerformed(ActionEvent e) {
3138            int cdlIndex = _selectConditionalBox.getSelectedIndex();
3139            if (cdlIndex > 0 && cdlIndex < _selectConditionalList.size()) {
3140                String cdlName = _selectConditionalList.get(cdlIndex);
3141                _variableNameField.setText(cdlName);
3142            }
3143        }
3144    };
3145
3146    transient ActionListener compareTypeBoxListener = new ActionListener() {
3147        @Override
3148        public void actionPerformed(ActionEvent e) {
3149            int selection = _variableCompareTypeBox.getSelectedIndex();
3150            compareTypeChanged(Conditional.Type.getMemoryItems().get(selection));
3151        }
3152    };
3153
3154    // ============ Edit Action Section ============
3155
3156    /**
3157     * Called once during class initialization to define the GUI objects. Where
3158     * possible, the combo box content is loaded.
3159     */
3160    void buildActionComponents() {
3161        // Item Type
3162        _actionItemBox = new JComboBox<>();
3163        for (Conditional.ItemType itemType : Conditional.ItemType.values()) {
3164            _actionItemBox.addItem(itemType);
3165        }
3166        JComboBoxUtil.setupComboBoxMaxRows(_actionItemBox);
3167        _actionItemBox.addActionListener(new ActionListener() {
3168            @Override
3169            public void actionPerformed(ActionEvent e) {
3170                Conditional.ItemType newActionItem =
3171                        _actionItemBox.getItemAt(_actionItemBox.getSelectedIndex());
3172                if (log.isDebugEnabled()) {
3173                    log.debug("_actionItemBox Listener: new = {}, curr = {}, row = {}",  // NOI18N
3174                            newActionItem, _curActionItem, _curNodeRow);
3175                }
3176                if (newActionItem != _curActionItem) {
3177                    if (_curNodeRow >= 0) {
3178                        _curAction = new DefaultConditionalAction();
3179                        _actionList.set(_curNodeRow, _curAction);
3180                    }
3181                    _curActionItem = newActionItem;
3182                }
3183                actionItemChanged(newActionItem);
3184            }
3185        });
3186
3187        // Item Name
3188        _actionNameField = new JTextField(20);
3189
3190        // Action Type Box
3191        _actionTypeBox = new JComboBox<>();
3192        JComboBoxUtil.setupComboBoxMaxRows(_actionTypeBox);
3193        _actionTypeBox.addItem(Conditional.Action.NONE);
3194
3195        // Action State Box
3196        _actionBox = new JComboBox<String>();
3197        _actionBox.addItem("");
3198
3199        // Short strings: Delay time, memory value/copy target
3200        _shortActionString = new JTextField(15);
3201
3202        // On Change / Trigger options
3203        _actionOptionBox = new JComboBox<String>();
3204
3205        // File Selector
3206        _actionSetButton = new JButton("..."); // "File" replaced by ...
3207        _actionSetButton.addActionListener(new ActionListener() {
3208            @Override
3209            public void actionPerformed(ActionEvent e) {
3210                validateAction();
3211                setFileLocation(e);
3212            }
3213        });
3214
3215        // File names, etc.
3216        _longActionString = new JTextField(30);
3217    }
3218
3219    // ------------ Make Action Edit Grid Panels ------------
3220
3221    /**
3222     * Create a one row grid with just the Action Item box. This is the base for
3223     * larger grids as well as the initial grid for new Actions.
3224     *
3225     * @param c the constraints object used for the grid construction
3226     */
3227    void makeEmptyActionGrid(GridBagConstraints c) {
3228        // Action item box
3229        c.gridy = 0;
3230        c.gridx = 0;
3231        c.anchor = java.awt.GridBagConstraints.EAST;
3232        JLabel row0Label = new JLabel(Bundle.getMessage("LabelActionItem"));  // NOI18N
3233        row0Label.setToolTipText(Bundle.getMessage("ActionItemHint"));  // NOI18N
3234        _gridPanel.add(row0Label, c);
3235        c.gridx = 1;
3236        c.anchor = java.awt.GridBagConstraints.WEST;
3237        _gridPanel.add(_actionItemBox, c);
3238    }
3239
3240    /**
3241     * Create the standard Name and Type rows.
3242     * The name field will be either a text field or a combo box.
3243     * The name field label is a variable to support run time changes.
3244     *
3245     * @param c The constraints object used for the grid construction
3246     * @param finalRow Controls whether the tigger combo box is included
3247     */
3248    void makeNameTypeActionGrid(GridBagConstraints c, boolean finalRow) {
3249        makeEmptyActionGrid(c);
3250
3251        Conditional.Action actionType = _curAction.getType();
3252        Conditional.ItemType itemType = actionType.getItemType();
3253
3254        // Name Field
3255        c.gridy = 1;
3256        c.gridx = 0;
3257        c.anchor = java.awt.GridBagConstraints.EAST;
3258        _gridPanel.add(_actionNameLabel, c);
3259        c.gridx = 1;
3260        c.anchor = java.awt.GridBagConstraints.WEST;
3261        if ((_selectionMode == SelectionMode.USECOMBO)
3262                && (itemType != Conditional.ItemType.AUDIO)) {
3263            _gridPanel.add(_comboNameBox, c);
3264        } else {
3265            _gridPanel.add(_actionNameField, c);
3266        }
3267
3268        // Action Type Box
3269        c.gridy = 2;
3270        c.gridx = 0;
3271        c.anchor = java.awt.GridBagConstraints.EAST;
3272        JLabel row5Label = new JLabel(Bundle.getMessage("LabelActionType"));  // NOI18N
3273        row5Label.setToolTipText(Bundle.getMessage("ActionTypeHint"));  // NOI18N
3274        _gridPanel.add(row5Label, c);
3275        c.gridx = 1;
3276        c.anchor = java.awt.GridBagConstraints.WEST;
3277        _gridPanel.add(_actionTypeBox, c);
3278
3279        if (itemType == Conditional.ItemType.NONE) {
3280            // Skip the change/trigger section for new Actions
3281            return;
3282        }
3283
3284        if (finalRow) {
3285            makeChangeTriggerActionGrid(c);
3286        }
3287    }
3288
3289    /**
3290     * Create the standard Type row.
3291     *
3292     * @param c The constraints object used for the grid construction
3293     * @param finalRow Controls whether the tigger combo box is included
3294     */
3295    void makeTypeActionGrid(GridBagConstraints c, boolean finalRow) {
3296        makeEmptyActionGrid(c);
3297
3298        Conditional.Action actionType = _curAction.getType();
3299        Conditional.ItemType itemType = actionType.getItemType();
3300
3301        // Action Type Box
3302        c.gridy = 1;
3303        c.gridx = 0;
3304        c.anchor = java.awt.GridBagConstraints.EAST;
3305        JLabel row5Label = new JLabel(Bundle.getMessage("LabelActionType"));  // NOI18N
3306        row5Label.setToolTipText(Bundle.getMessage("ActionTypeHint"));  // NOI18N
3307        _gridPanel.add(row5Label, c);
3308        c.gridx = 1;
3309        c.anchor = java.awt.GridBagConstraints.WEST;
3310        _gridPanel.add(_actionTypeBox, c);
3311
3312        if (itemType == Conditional.ItemType.NONE) {
3313            // Skip the change/trigger section for new Actions
3314            return;
3315        }
3316
3317        if (finalRow) {
3318            makeChangeTriggerActionGrid(c);
3319        }
3320    }
3321
3322    /**
3323     * Add the action box to the grid.
3324     *
3325     * @param c        The constraints object used for the grid construction
3326     * @param finalRow Controls whether the tigger combo box is included
3327     */
3328    void makeStandardActionGrid(GridBagConstraints c, boolean finalRow) {
3329        makeNameTypeActionGrid(c, false);
3330
3331        // Action Box
3332        c.gridy = 3;
3333        c.gridx = 0;
3334        c.anchor = java.awt.GridBagConstraints.EAST;
3335        _gridPanel.add(_actionBoxLabel, c);
3336        c.gridx = 1;
3337        c.anchor = java.awt.GridBagConstraints.WEST;
3338        _gridPanel.add(_actionBox, c);
3339
3340        if (finalRow) {
3341            makeChangeTriggerActionGrid(c);
3342        }
3343    }
3344
3345    /**
3346     * Add the short name field to the grid.
3347     *
3348     * @param c          The constraints object used for the grid construction
3349     * @param includeBox Controls whether the normal action type combo box is
3350     *                   included
3351     */
3352    void makeShortFieldActionGrid(GridBagConstraints c, boolean includeBox) {
3353        if (includeBox) {
3354            makeStandardActionGrid(c, false);
3355        } else {
3356            makeNameTypeActionGrid(c, false);
3357        }
3358
3359        // Add the short text field
3360        c.gridy = 4;
3361        c.gridx = 0;
3362        c.anchor = java.awt.GridBagConstraints.EAST;
3363        _gridPanel.add(_shortActionLabel, c);
3364        c.gridx = 1;
3365        c.anchor = java.awt.GridBagConstraints.WEST;
3366        _gridPanel.add(_shortActionString, c);
3367
3368        makeChangeTriggerActionGrid(c);
3369    }
3370
3371    /**
3372     * Just a short text field, no name field. Used by set clock and jython
3373     * command.
3374     *
3375     * @param c The constraints object used for the grid construction
3376     */
3377    void makeTypeShortActionGrid(GridBagConstraints c) {
3378        makeTypeActionGrid(c, false);
3379
3380        // Add the short text field
3381        c.gridy = 2;
3382        c.gridx = 0;
3383        c.anchor = java.awt.GridBagConstraints.EAST;
3384        _gridPanel.add(_shortActionLabel, c);
3385        c.gridx = 1;
3386        c.anchor = java.awt.GridBagConstraints.WEST;
3387        _gridPanel.add(_shortActionString, c);
3388
3389        makeChangeTriggerActionGrid(c);
3390    }
3391
3392    /**
3393     * Add the file selection components.
3394     *
3395     * @param c The constraints object used for the grid construction
3396     */
3397    void makeFileActionGrid(GridBagConstraints c) {
3398        makeTypeActionGrid(c, false);
3399
3400        // Add the file selecttor
3401        c.gridy = 2;
3402        c.gridx = 0;
3403        c.anchor = java.awt.GridBagConstraints.EAST;
3404        _gridPanel.add(_shortActionLabel, c);
3405        c.gridx = 1;
3406        c.anchor = java.awt.GridBagConstraints.WEST;
3407        _gridPanel.add(_actionSetButton, c);
3408
3409        c.gridwidth = 2;    // span both columns
3410        // Add the long text field
3411        c.gridy = 3;
3412        c.gridx = 0;
3413        c.anchor = java.awt.GridBagConstraints.CENTER;
3414        _gridPanel.add(_longActionString, c);
3415        c.gridwidth = 1;
3416
3417        makeChangeTriggerActionGrid(c);
3418    }
3419
3420    /**
3421     * Add the change/trigger box the grid. This is the last item in an Action
3422     * and is usually called from one of the other entry points.
3423     *
3424     * @param c The constraints object used for the grid construction
3425     */
3426    void makeChangeTriggerActionGrid(GridBagConstraints c) {
3427        // Action item box
3428        c.gridy = 9;
3429        c.gridx = 0;
3430        c.anchor = java.awt.GridBagConstraints.EAST;
3431        JLabel row0Label = new JLabel(Bundle.getMessage("LabelActionOption"));  // NOI18N
3432        row0Label.setToolTipText(Bundle.getMessage("ActionOptionHint"));  // NOI18N
3433        _gridPanel.add(row0Label, c);
3434        c.gridx = 1;
3435        c.anchor = java.awt.GridBagConstraints.WEST;
3436        _gridPanel.add(_actionOptionBox, c);
3437    }
3438
3439    // ------------ Main Action methods ------------
3440
3441    /**
3442     * Set display to show current action (curAction) parameters.
3443     */
3444    void initializeActionVariables() {
3445        Conditional.Action actionType = _curAction.getType();
3446        Conditional.ItemType itemType = actionType.getItemType();
3447        log.debug("initializeActionVariables: itemType= {}, actionType= {}", itemType, actionType);  // NOI18N
3448        _actionItemBox.setSelectedItem(itemType);
3449        _actionNameField.setText(_curAction.getDeviceName());
3450        switch (itemType) {
3451            case NONE:
3452                _actionNameField.setText("");
3453                break;
3454
3455            case SENSOR:
3456                _actionTypeBox.setSelectedItem(actionType);
3457                if ((actionType == Conditional.Action.RESET_DELAYED_SENSOR)
3458                        || (actionType == Conditional.Action.DELAYED_SENSOR)) {
3459                    _shortActionString.setText(_curAction.getActionString());
3460                }
3461                if (actionType == Conditional.Action.SET_SENSOR
3462                        || actionType == Conditional.Action.DELAYED_SENSOR
3463                        || actionType == Conditional.Action.RESET_DELAYED_SENSOR) {
3464                    if (_curAction.getActionData() == Sensor.ACTIVE) {
3465                        _actionBox.setSelectedIndex(0);
3466                    } else if (_curAction.getActionData() == Sensor.INACTIVE) {
3467                        _actionBox.setSelectedIndex(1);
3468                    } else if (_curAction.getActionData() == Route.TOGGLE) {
3469                        _actionBox.setSelectedIndex(2);
3470                    }
3471                }
3472                break;
3473
3474            case TURNOUT:
3475                _actionTypeBox.setSelectedItem(actionType);
3476                if ((actionType == Conditional.Action.RESET_DELAYED_TURNOUT)
3477                        || (actionType == Conditional.Action.DELAYED_TURNOUT)) {
3478                    _shortActionString.setText(_curAction.getActionString());
3479                }
3480                if ((actionType == Conditional.Action.SET_TURNOUT)
3481                        || (actionType == Conditional.Action.RESET_DELAYED_TURNOUT)
3482                        || (actionType == Conditional.Action.DELAYED_TURNOUT)) {
3483                    if (_curAction.getActionData() == Turnout.CLOSED) {
3484                        _actionBox.setSelectedIndex(0);
3485                    } else if (_curAction.getActionData() == Turnout.THROWN) {
3486                        _actionBox.setSelectedIndex(1);
3487                    } else if (_curAction.getActionData() == Route.TOGGLE) {
3488                        _actionBox.setSelectedIndex(2);
3489                    }
3490                } else if (actionType == Conditional.Action.LOCK_TURNOUT) {
3491                    if (_curAction.getActionData() == Turnout.UNLOCKED) {
3492                        _actionBox.setSelectedIndex(0);
3493                    } else if (_curAction.getActionData() == Turnout.LOCKED) {
3494                        _actionBox.setSelectedIndex(1);
3495                    } else if (_curAction.getActionData() == Route.TOGGLE) {
3496                        _actionBox.setSelectedIndex(2);
3497                    }
3498                }
3499                break;
3500
3501            case LIGHT:
3502                _actionTypeBox.setSelectedItem(actionType);
3503                if (actionType == Conditional.Action.SET_LIGHT) {
3504                    if (_curAction.getActionData() == Light.ON) {
3505                        _actionBox.setSelectedIndex(0);
3506                    } else if (_curAction.getActionData() == Light.OFF) {
3507                        _actionBox.setSelectedIndex(1);
3508                    } else if (_curAction.getActionData() == Route.TOGGLE) {
3509                        _actionBox.setSelectedIndex(2);
3510                    }
3511                } else if ((actionType == Conditional.Action.SET_LIGHT_INTENSITY)
3512                        || (actionType == Conditional.Action.SET_LIGHT_TRANSITION_TIME)) {
3513                    _shortActionString.setText(_curAction.getActionString());
3514                }
3515                break;
3516
3517            case SIGNALHEAD:
3518                _actionTypeBox.setSelectedItem(actionType);
3519                if (actionType == Conditional.Action.SET_SIGNAL_APPEARANCE) {
3520                    loadJComboBoxWithHeadAppearances(_actionBox, _actionNameField.getText().trim());
3521                }
3522                break;
3523
3524            case CLOCK:
3525                _actionTypeBox.setSelectedItem(actionType);
3526                if (actionType == Conditional.Action.SET_FAST_CLOCK_TIME) {
3527                    int time = _curAction.getActionData();
3528                    _longActionString.setText(formatTime(time / 60, time - ((time / 60) * 60)));
3529                    _actionNameField.setText("");
3530                }
3531                break;
3532
3533            case MEMORY:
3534                _actionTypeBox.setSelectedItem(actionType);
3535                _shortActionString.setText(_curAction.getActionString());
3536                break;
3537
3538            case WARRANT:
3539                _actionTypeBox.setSelectedItem(actionType);
3540                if (actionType == Conditional.Action.CONTROL_TRAIN) {
3541                    switch (_curAction.getActionData()) {
3542                        case Warrant.HALT:
3543                            _actionBox.setSelectedIndex(0);
3544                            break;
3545                        case Warrant.RESUME:
3546                            _actionBox.setSelectedIndex(1);
3547                            break;
3548                        case Warrant.RETRY_FWD:
3549                            _actionBox.setSelectedIndex(2);
3550                            break;
3551                        case Warrant.SPEED_UP:
3552                            _actionBox.setSelectedIndex(3);
3553                            break;
3554                        case Warrant.STOP:
3555                            _actionBox.setSelectedIndex(4);
3556                            break;
3557                        case Warrant.ESTOP:
3558                            _actionBox.setSelectedIndex(5);
3559                            break;
3560                        case Warrant.ABORT:
3561                            _actionBox.setSelectedIndex(6);
3562                            break;
3563                        default:
3564                            log.warn("Unexpected _curAction.getActionData() of {}", _curAction.getActionData());  // NOI18N
3565                    }
3566                } else if (actionType == Conditional.Action.SET_TRAIN_ID
3567                        || actionType == Conditional.Action.SET_TRAIN_NAME) {
3568                    _shortActionString.setText(_curAction.getActionString());
3569                }
3570                break;
3571
3572            case OBLOCK:
3573                _actionTypeBox.setSelectedItem(actionType);
3574                if (actionType == Conditional.Action.SET_BLOCK_VALUE
3575                        || actionType == Conditional.Action.SET_TRAIN_NAME
3576                        || actionType == Conditional.Action.GET_TRAIN_LOCATION) {
3577                    _shortActionString.setText(_curAction.getActionString());
3578                }
3579                break;
3580
3581            case ENTRYEXIT:
3582                _actionNameField.setText(_curAction.getBean().getUserName());
3583                _actionTypeBox.setSelectedItem(actionType);
3584                break;
3585
3586            case AUDIO:
3587                _actionTypeBox.setSelectedItem(actionType);
3588                if (actionType == Conditional.Action.PLAY_SOUND) {
3589                    _longActionString.setText(_curAction.getActionString());
3590                } else if (actionType == Conditional.Action.CONTROL_AUDIO) {
3591                    switch (_curAction.getActionData()) {
3592                        case Audio.CMD_PLAY:
3593                            _actionBox.setSelectedIndex(0);
3594                            break;
3595                        case Audio.CMD_STOP:
3596                            _actionBox.setSelectedIndex(1);
3597                            break;
3598                        case Audio.CMD_PLAY_TOGGLE:
3599                            _actionBox.setSelectedIndex(2);
3600                            break;
3601                        case Audio.CMD_PAUSE:
3602                            _actionBox.setSelectedIndex(3);
3603                            break;
3604                        case Audio.CMD_RESUME:
3605                            _actionBox.setSelectedIndex(4);
3606                            break;
3607                        case Audio.CMD_PAUSE_TOGGLE:
3608                            _actionBox.setSelectedIndex(5);
3609                            break;
3610                        case Audio.CMD_REWIND:
3611                            _actionBox.setSelectedIndex(6);
3612                            break;
3613                        case Audio.CMD_FADE_IN:
3614                            _actionBox.setSelectedIndex(7);
3615                            break;
3616                        case Audio.CMD_FADE_OUT:
3617                            _actionBox.setSelectedIndex(8);
3618                            break;
3619                        case Audio.CMD_RESET_POSITION:
3620                            _actionBox.setSelectedIndex(9);
3621                            break;
3622                        default:
3623                            log.warn("Unexpected _curAction.getActionData() of {}", _curAction.getActionData());  // NOI18N
3624                            break;
3625                    }
3626                }
3627                break;
3628
3629            case SCRIPT:
3630                _actionTypeBox.setSelectedItem(actionType);
3631                if (actionType == Conditional.Action.RUN_SCRIPT) {
3632                    _longActionString.setText(_curAction.getActionString());
3633                } else if (actionType == Conditional.Action.JYTHON_COMMAND) {
3634                    _shortActionString.setText(_curAction.getActionString());
3635                }
3636                break;
3637
3638            case SIGNALMAST:
3639            case LOGIX:
3640            case OTHER: // ACTION_TRIGGER_ROUTE
3641                _actionTypeBox.setSelectedItem(actionType);
3642                break;
3643
3644            default:
3645                log.error("Unhandled type: {}", itemType);  // NOI18N
3646                break;
3647        }
3648        _actionOptionBox.setSelectedIndex(_curAction.getOption() - 1);
3649    }
3650
3651    /**
3652     * Respond to a change in an Action Type comboBox.
3653     * <p>
3654     * Set components visible for the selected type.
3655     *
3656     * @param type index of the newly selected Action type
3657     */
3658    void actionItemChanged(Conditional.ItemType type) {
3659        Conditional.Action actionType = _curAction.getType();
3660        log.debug("actionItemChanged: itemType= {}, actionType= {}", type, actionType);  // NOI18N
3661        _detailGrid.setVisible(false);
3662        _actionTypeBox.removeActionListener(_actionTypeListener);
3663        _shortActionString.setText("");
3664        _longActionString.setText("");
3665        _actionTypeBox.removeAllItems();
3666        _actionTypeBox.addItem(Conditional.Action.NONE);
3667        _actionBox.removeAllItems();
3668        Conditional.ItemType itemType = actionType.getItemType();
3669        if (type != Conditional.ItemType.NONE) {  // actionItem listener choice overrides current item
3670            itemType = type;
3671        }
3672        if (itemType != actionType.getItemType()) {
3673            actionType = Conditional.Action.NONE;    // chosen item type does not support action type
3674        }
3675
3676        _actionNameField.removeActionListener(actionSignalHeadNameListener);
3677        _actionNameField.removeActionListener(actionSignalMastNameListener);
3678
3679        if (_comboNameBox != null) {
3680            for (ActionListener item : _comboNameBox.getActionListeners()) {
3681                _comboNameBox.removeActionListener(item);
3682            }
3683            _comboNameBox.removeFocusListener(detailFocusEvent);
3684        }
3685        setPickWindow("Action", itemType);  // NOI18N
3686
3687        switch (itemType) {
3688            case NONE:
3689                makeDetailGrid("EmptyAction");  // NOI18N
3690                break;
3691
3692            case TURNOUT:
3693                _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintTurnout"));  // NOI18N
3694                String turnoutGrid = "NameTypeAction";  // NOI18N
3695                boolean delayTurnout = false;
3696
3697                for (Conditional.Action action : Conditional.Action.getTurnoutItems()) {
3698                    _actionTypeBox.addItem(action);
3699                }
3700
3701                if ((actionType == Conditional.Action.RESET_DELAYED_TURNOUT)
3702                        || (actionType == Conditional.Action.DELAYED_TURNOUT)) {
3703                    delayTurnout = true;
3704                    _shortActionLabel.setText(Bundle.getMessage("LabelDelayTime"));  // NOI18N
3705                    _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintDelayedTurnout"));  // NOI18N
3706                }
3707                if ((actionType == Conditional.Action.SET_TURNOUT)
3708                        || (actionType == Conditional.Action.RESET_DELAYED_TURNOUT)
3709                        || (actionType == Conditional.Action.DELAYED_TURNOUT)) {
3710                    turnoutGrid = (delayTurnout) ? "ShortFieldAction" : "StandardAction";  // NOI18N
3711                    _actionBoxLabel.setText(Bundle.getMessage("LabelActionTurnout"));  // NOI18N
3712                    _actionBoxLabel.setToolTipText(Bundle.getMessage("TurnoutSetHint"));  // NOI18N
3713                    _actionBox.addItem(Bundle.getMessage("TurnoutStateClosed"));  // NOI18N
3714                    _actionBox.addItem(Bundle.getMessage("TurnoutStateThrown"));  // NOI18N
3715                    _actionBox.addItem(Bundle.getMessage("Toggle"));  // NOI18N
3716                } else if (actionType == Conditional.Action.LOCK_TURNOUT) {
3717                    turnoutGrid = (delayTurnout) ? "ShortFieldAction" : "StandardAction";  // NOI18N
3718                    _actionBoxLabel.setText(Bundle.getMessage("LabelActionLock"));  // NOI18N
3719                    _actionBoxLabel.setToolTipText(Bundle.getMessage("LockSetHint"));  // NOI18N
3720                    _actionBox.addItem(Bundle.getMessage("TurnoutUnlock"));  // NOI18N
3721                    _actionBox.addItem(Bundle.getMessage("TurnoutLock"));  // NOI18N
3722                    _actionBox.addItem(Bundle.getMessage("Toggle"));  // NOI18N
3723                } else if ((actionType == Conditional.Action.CANCEL_TURNOUT_TIMERS)
3724                        || (actionType == Conditional.Action.NONE)) {
3725                    turnoutGrid = "NameTypeActionFinal";  // NOI18N
3726                }
3727
3728                setActionNameBox(itemType);
3729                makeDetailGrid(turnoutGrid);
3730                break;
3731
3732            case SENSOR:
3733                _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintSensor"));  // NOI18N
3734                String sensorGrid = "NameTypeAction";  // NOI18N
3735                boolean delaySensor = false;
3736
3737                for (Conditional.Action action : Conditional.Action.getSensorItems()) {
3738                    _actionTypeBox.addItem(action);
3739                }
3740                if ((actionType == Conditional.Action.RESET_DELAYED_SENSOR)
3741                        || (actionType == Conditional.Action.DELAYED_SENSOR)) {
3742                    delaySensor = true;
3743                    _shortActionLabel.setText(Bundle.getMessage("LabelDelayTime"));  // NOI18N
3744                    _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintDelayedSensor"));  // NOI18N
3745                }
3746                if ((actionType == Conditional.Action.SET_SENSOR)
3747                        || (actionType == Conditional.Action.RESET_DELAYED_SENSOR)
3748                        || (actionType == Conditional.Action.DELAYED_SENSOR)) {
3749                    sensorGrid = (delaySensor) ? "ShortFieldAction" : "StandardAction";  // NOI18N
3750                    _actionBoxLabel.setText(Bundle.getMessage("LabelActionSensor"));  // NOI18N
3751                    _actionBoxLabel.setToolTipText(Bundle.getMessage("SensorSetHint"));  // NOI18N
3752                    _actionBox.addItem(Bundle.getMessage("SensorStateActive"));  // NOI18N
3753                    _actionBox.addItem(Bundle.getMessage("SensorStateInactive"));  // NOI18N
3754                    _actionBox.addItem(Bundle.getMessage("Toggle"));  // NOI18N
3755                } else if ((actionType == Conditional.Action.CANCEL_SENSOR_TIMERS)
3756                        || (actionType == Conditional.Action.NONE)) {
3757                    sensorGrid = "NameTypeActionFinal";  // NOI18N
3758                }
3759
3760                setActionNameBox(itemType);
3761                makeDetailGrid(sensorGrid);
3762                break;
3763
3764            case SIGNALHEAD:
3765                _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintSignal"));  // NOI18N
3766                String signalHeadGrid = "NameTypeAction";  // NOI18N
3767                _actionNameField.addActionListener(actionSignalHeadNameListener);
3768
3769                for (Conditional.Action action : Conditional.Action.getSignalHeadItems()) {
3770                    _actionTypeBox.addItem(action);
3771                }
3772
3773                if (actionType == Conditional.Action.SET_SIGNAL_APPEARANCE) {
3774                    signalHeadGrid = "StandardAction";  // NOI18N
3775                    _actionBoxLabel.setText(Bundle.getMessage("LabelActionSignal"));  // NOI18N
3776                    _actionBoxLabel.setToolTipText(Bundle.getMessage("SignalSetHint"));  // NOI18N
3777                    loadJComboBoxWithHeadAppearances(_actionBox, _actionNameField.getText().trim());
3778                    _actionBox.setSelectedItem(_curAction.getActionDataString());
3779                } else if (actionType != Conditional.Action.NONE) {
3780                    signalHeadGrid = "NameTypeActionFinal";  // NOI18N
3781                }
3782
3783                setActionNameBox(itemType);
3784                makeDetailGrid(signalHeadGrid);
3785                break;
3786
3787            case SIGNALMAST:
3788                _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintSignalMast"));  // NOI18N
3789                String signalMastGrid = "NameTypeAction";  // NOI18N
3790                _actionNameField.addActionListener(actionSignalMastNameListener);
3791
3792                for (Conditional.Action action : Conditional.Action.getSignalMastItems()) {
3793                    _actionTypeBox.addItem(action);
3794                }
3795
3796                if (actionType == Conditional.Action.SET_SIGNALMAST_ASPECT) {
3797                    signalMastGrid = "StandardAction";  // NOI18N
3798                    _actionBoxLabel.setText(Bundle.getMessage("LabelSignalAspect"));  // NOI18N
3799                    _actionBoxLabel.setToolTipText(Bundle.getMessage("SignalMastSetHint"));  // NOI18N
3800                    loadJComboBoxWithMastAspects(_actionBox, _actionNameField.getText().trim());
3801                    _actionBox.setSelectedItem(_curAction.getActionDataString());
3802                } else if (actionType != Conditional.Action.NONE) {
3803                    signalMastGrid = "NameTypeActionFinal";  // NOI18N
3804                }
3805
3806                setActionNameBox(itemType);
3807                makeDetailGrid(signalMastGrid);
3808                break;
3809
3810            case LIGHT:
3811                _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintLight"));  // NOI18N
3812                String lightGrid = "NameTypeAction";  // NOI18N
3813
3814                for (Conditional.Action action : Conditional.Action.getLightItems()) {
3815                    _actionTypeBox.addItem(action);
3816                }
3817
3818                if (actionType == Conditional.Action.SET_LIGHT_INTENSITY) {
3819                    lightGrid = "ShortFieldNoBoxAction";  // NOI18N
3820                    _shortActionLabel.setText(Bundle.getMessage("LabelLightIntensity"));  // NOI18N
3821                    _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintLightIntensity"));  // NOI18N
3822                } else if (actionType == Conditional.Action.SET_LIGHT_TRANSITION_TIME) {
3823                    lightGrid = "ShortFieldNoBoxAction";  // NOI18N
3824                    _shortActionLabel.setText(Bundle.getMessage("LabelTransitionTime"));  // NOI18N
3825                    _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintLightTransitionTime"));  // NOI18N
3826                } else if (actionType == Conditional.Action.SET_LIGHT) {
3827                    lightGrid = "StandardAction";  // NOI18N
3828                    _actionBoxLabel.setText(Bundle.getMessage("LabelActionLight"));  // NOI18N
3829                    _actionBoxLabel.setToolTipText(Bundle.getMessage("LightSetHint"));  // NOI18N
3830                    _actionBox.addItem(Bundle.getMessage("LightOn"));  // NOI18N
3831                    _actionBox.addItem(Bundle.getMessage("LightOff")); // NOI18N
3832                    _actionBox.addItem(Bundle.getMessage("Toggle"));   // NOI18N
3833                }
3834
3835                setActionNameBox(itemType);
3836                makeDetailGrid(lightGrid);
3837                break;
3838
3839            case MEMORY:
3840                _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintMemory"));  // NOI18N
3841                String memoryGrid = "NameTypeAction";  // NOI18N
3842
3843                for (Conditional.Action action : Conditional.Action.getMemoryItems()) {
3844                    _actionTypeBox.addItem(action);
3845                }
3846
3847                if (actionType == Conditional.Action.COPY_MEMORY) {
3848                    memoryGrid = "ShortFieldNoBoxAction";  // NOI18N
3849                    _shortActionLabel.setText(Bundle.getMessage("LabelMemoryLocation"));  // NOI18N
3850                    _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintToMemory"));  // NOI18N
3851                } else if (actionType == Conditional.Action.SET_MEMORY) {
3852                    memoryGrid = "ShortFieldNoBoxAction";  // NOI18N
3853                    _shortActionLabel.setText(Bundle.getMessage("LabelValue"));  // NOI18N
3854                    _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintMemory"));  // NOI18N
3855                }
3856
3857                setActionNameBox(itemType);
3858                makeDetailGrid(memoryGrid);
3859                break;
3860
3861            case CLOCK:
3862                String clockGrid = "TypeAction";  // NOI18N
3863
3864                for (Conditional.Action action : Conditional.Action.getClockItems()) {
3865                    _actionTypeBox.addItem(action);
3866                }
3867
3868                if (actionType == Conditional.Action.SET_FAST_CLOCK_TIME) {
3869                    clockGrid = "TypeShortAction";  // NOI18N
3870                    _shortActionLabel.setText(Bundle.getMessage("LabelSetTime"));  // NOI18N
3871                    _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintTime"));  // NOI18N
3872                } else if ((actionType == Conditional.Action.START_FAST_CLOCK)
3873                        || (actionType == Conditional.Action.STOP_FAST_CLOCK)) {
3874                    clockGrid = "TypeActionFinal";  // NOI18N
3875                }
3876
3877                makeDetailGrid(clockGrid);
3878                break;
3879
3880            case LOGIX:
3881                _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintLogix"));  // NOI18N
3882                String logixGrid = "NameTypeAction";  // NOI18N
3883
3884                for (Conditional.Action action : Conditional.Action.getLogixItems()) {
3885                    _actionTypeBox.addItem(action);
3886                }
3887
3888                if ((actionType == Conditional.Action.ENABLE_LOGIX)
3889                        || (actionType == Conditional.Action.DISABLE_LOGIX)) {
3890                    logixGrid = "NameTypeActionFinal";  // NOI18N
3891                }
3892
3893                setActionNameBox(itemType);
3894                makeDetailGrid(logixGrid);
3895                break;
3896
3897            case WARRANT:
3898                _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintWarrant"));  // NOI18N
3899                String warrantGrid = "NameTypeAction";  // NOI18N
3900
3901                for (Conditional.Action action : Conditional.Action.getWarrantItems()) {
3902                    _actionTypeBox.addItem(action);
3903                }
3904
3905                if (actionType == Conditional.Action.CONTROL_TRAIN) {
3906                    warrantGrid = "StandardAction";  // NOI18N
3907                    _actionBoxLabel.setText(Bundle.getMessage("LabelControlTrain"));  // NOI18N
3908                    _actionBoxLabel.setToolTipText(Bundle.getMessage("DataHintTrainControl"));  // NOI18N
3909                    _actionBox.addItem(Bundle.getMessage("WarrantHalt"));
3910                    _actionBox.addItem(Bundle.getMessage("WarrantResume"));
3911                    _actionBox.addItem(Bundle.getMessage("WarrantMoveToNext"));
3912                    _actionBox.addItem(Bundle.getMessage("WarrantSpeedUp"));
3913                    _actionBox.addItem(Bundle.getMessage("WarrantStop"));
3914                    _actionBox.addItem(Bundle.getMessage("WarantEStop"));
3915                    _actionBox.addItem(Bundle.getMessage("WarrantAbort"));
3916                } else if (actionType == Conditional.Action.SET_TRAIN_ID
3917                        || actionType == Conditional.Action.SET_TRAIN_NAME
3918                        || actionType == Conditional.Action.GET_TRAIN_LOCATION) {
3919                    warrantGrid = "ShortFieldNoBoxAction";  // NOI18N
3920                    if (actionType == Conditional.Action.SET_TRAIN_ID) {
3921                        _shortActionLabel.setText(Bundle.getMessage("LabelTrainId"));  // NOI18N
3922                        _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintTrainId"));  // NOI18N
3923                    } else if (actionType == Conditional.Action.SET_TRAIN_NAME) {
3924                        _shortActionLabel.setText(Bundle.getMessage("LabelTrainName"));  // NOI18N
3925                        _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintTrainName"));  // NOI18N
3926                    } else if (actionType == Conditional.Action.GET_TRAIN_LOCATION) {
3927                        _shortActionLabel.setText(Bundle.getMessage("LabelMemoryLocation"));  // NOI18N
3928                        _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintToMemory"));  // NOI18N
3929                    }
3930                }
3931                setActionNameBox(itemType);
3932                makeDetailGrid(warrantGrid);
3933                break;
3934
3935            case OBLOCK:
3936                _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintOBlock"));  // NOI18N
3937                String oblockGrid = "NameTypeAction";  // NOI18N
3938
3939                for (Conditional.Action action : Conditional.Action.getOBlockItems()) {
3940                    _actionTypeBox.addItem(action);
3941                }
3942                if (actionType == Conditional.Action.SET_BLOCK_VALUE) {
3943                    _shortActionLabel.setText(Bundle.getMessage("LabelBlockValue"));  // NOI18N
3944                    _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintBlockValue"));  // NOI18N
3945                    oblockGrid = "ShortFieldNoBoxAction";  // NOI18N
3946                } else if(actionType == Conditional.Action.GET_BLOCK_TRAIN_NAME
3947                        || actionType == Conditional.Action.GET_BLOCK_WARRANT) {
3948                    _shortActionLabel.setText(Bundle.getMessage("LabelMemoryLocation"));  // NOI18N
3949                    _shortActionLabel.setToolTipText(Bundle.getMessage("DataHintToMemory"));  // NOI18N
3950                    oblockGrid = "ShortFieldNoBoxAction";  // NOI18N
3951                } else {
3952                    oblockGrid = "NameTypeActionFinal";  // NOI18N
3953                }
3954
3955                setActionNameBox(itemType);
3956                makeDetailGrid(oblockGrid);
3957                break;
3958
3959            case ENTRYEXIT:
3960                for (Conditional.Action action : Conditional.Action.getEntryExitItems()) {
3961                    _actionTypeBox.addItem(action);
3962                }
3963                setActionNameBox(itemType);
3964                makeDetailGrid("NameTypeActionFinal");
3965                break;
3966
3967            case AUDIO:
3968                _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintOBlock"));  // NOI18N
3969                String audioGrid = "TypeAction";  // NOI18N
3970
3971                for (Conditional.Action action : Conditional.Action.getAudioItems()) {
3972                    _actionTypeBox.addItem(action);
3973                }
3974
3975                if (actionType == Conditional.Action.PLAY_SOUND) {
3976                    audioGrid = "FileAction";  // NOI18N
3977                    _shortActionLabel.setText(Bundle.getMessage("LabelSelectFile"));  // NOI18N
3978                    _actionSetButton.setToolTipText(Bundle.getMessage("SetHintSound"));  // NOI18N
3979                } else if (actionType == Conditional.Action.CONTROL_AUDIO) {
3980                    audioGrid = "StandardAction";  // NOI18N
3981                    _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintAudio"));  // NOI18N
3982                    _actionBoxLabel.setText(Bundle.getMessage("LabelActionAudio"));  // NOI18N
3983                    _actionBoxLabel.setToolTipText(Bundle.getMessage("SetHintAudio"));  // NOI18N
3984                    _actionBox.addItem(Bundle.getMessage("AudioSourcePlay"));        // NOI18N
3985                    _actionBox.addItem(Bundle.getMessage("AudioSourceStop"));        // NOI18N
3986                    _actionBox.addItem(Bundle.getMessage("AudioSourcePlayToggle"));  // NOI18N
3987                    _actionBox.addItem(Bundle.getMessage("AudioSourcePause"));       // NOI18N
3988                    _actionBox.addItem(Bundle.getMessage("AudioSourceResume"));      // NOI18N
3989                    _actionBox.addItem(Bundle.getMessage("AudioSourcePauseToggle")); // NOI18N
3990                    _actionBox.addItem(Bundle.getMessage("AudioSourceRewind"));      // NOI18N
3991                    _actionBox.addItem(Bundle.getMessage("AudioSourceFadeIn"));      // NOI18N
3992                    _actionBox.addItem(Bundle.getMessage("AudioSourceFadeOut"));     // NOI18N
3993                    _actionBox.addItem(Bundle.getMessage("AudioResetPosition"));     // NOI18N
3994                }
3995
3996                makeDetailGrid(audioGrid);
3997                break;
3998
3999            case SCRIPT:
4000                String scriptGrid = "TypeAction";  // NOI18N
4001
4002                for (Conditional.Action action : Conditional.Action.getScriptItems()) {
4003                    _actionTypeBox.addItem(action);
4004                }
4005
4006                if (actionType == Conditional.Action.RUN_SCRIPT) {
4007                    scriptGrid = "FileAction";  // NOI18N
4008                    _shortActionLabel.setText(Bundle.getMessage("LabelSelectFile"));  // NOI18N
4009                    _actionSetButton.setToolTipText(Bundle.getMessage("SetHintScript"));  // NOI18N
4010                } else if (actionType == Conditional.Action.JYTHON_COMMAND) {
4011                    scriptGrid = "TypeShortAction";  // NOI18N
4012                    _shortActionLabel.setText(Bundle.getMessage("LabelScriptCommand"));  // NOI18N
4013                    _shortActionLabel.setToolTipText(Bundle.getMessage("SetHintJythonCmd"));  // NOI18N
4014                }
4015
4016                makeDetailGrid(scriptGrid);
4017                break;
4018
4019            case OTHER:
4020                String otherGrid = "TypeAction";  // NOI18N
4021
4022                for (Conditional.Action action : Conditional.Action.getOtherItems()) {
4023                    _actionTypeBox.addItem(action);
4024                }
4025
4026                if (actionType == Conditional.Action.TRIGGER_ROUTE) {
4027                    otherGrid = "NameTypeActionFinal";  // NOI18N
4028                    _actionNameLabel.setToolTipText(Bundle.getMessage("NameHintRoute"));  // NOI18N
4029                }
4030
4031                setActionNameBox(itemType);
4032                makeDetailGrid(otherGrid);
4033                break;
4034
4035            default:
4036                break;
4037        }
4038        _actionTypeListener.setItemType(itemType);
4039        _actionTypeBox.addActionListener(_actionTypeListener);
4040    }
4041
4042    /**
4043     * Update the name combo box selection based on the current contents of the
4044     * name field.
4045     *
4046     * @since 4.7.3
4047     * @param itemType The item type, such as sensor or turnout.
4048     */
4049    void setActionNameBox(Conditional.ItemType itemType) {
4050        if (_selectionMode != SelectionMode.USECOMBO) {
4051            return;
4052        }
4053        _comboNameBox = createNameBox(itemType);
4054        if (_comboNameBox == null) {
4055            return;
4056        }
4057        // Select the current entry
4058        _comboNameBox.setSelectedItemByName(_curAction.getDeviceName());
4059        _comboNameBox.addActionListener(new NameBoxListener(_actionNameField));
4060        _comboNameBox.addFocusListener(detailFocusEvent);
4061    }
4062
4063    // ------------ Action detail methods ------------
4064
4065    /**
4066     * Respond to Cancel action button and window closer of the Edit Action
4067     * window.
4068     * <p>
4069     * Also does cleanup of Update and Delete buttons.
4070     */
4071    void cancelEditAction() {
4072        if (_newActionItem) {
4073            _newActionItem = false;
4074            deletePressed();
4075        }
4076        cleanUpAction();
4077    }
4078
4079    /**
4080     * Respond to Update button.
4081     */
4082    void updateAction() {
4083        if (!validateAction()) {
4084            return;
4085        }
4086        _newActionItem = false;
4087        updateActionList();
4088
4089        // Update the Action node
4090        _curNode.setText(_curAction.description(_triggerMode));
4091        _cdlModel.nodeChanged(_curNode);
4092        cleanUpAction();
4093    }
4094
4095    /**
4096     * Clean up Update and Delete Action buttons.
4097     */
4098    void cleanUpAction() {
4099        setMoveButtons();
4100    }
4101
4102    /*.*
4103     * Convert user setting in Conditional Action configuration pane to integer
4104     * for processing.
4105     *
4106     * @param itemType            value for current item type
4107     * @param actionTypeSelection index of selected item in configuration
4108     *                            comboBox
4109     * @return integer representing the selected action
4110     */
4111/*    static Conditional.Action getActionTypeFromBox(Conditional.ItemType itemType, int actionTypeSelection) {
4112//        if (itemType < 0 || actionTypeSelection < 0) {
4113//            return Conditional.Action.NONE;
4114//        }
4115        if (actionTypeSelection < 0) {
4116            return Conditional.Action.NONE;
4117        }
4118        switch (itemType) {
4119            case SENSOR:
4120                return Conditional.Action.getSensorItems().get(actionTypeSelection);
4121            case TURNOUT:
4122                return Conditional.Action.getTurnoutItems().get(actionTypeSelection);
4123            case LIGHT:
4124                return Conditional.Action.getLightItems().get(actionTypeSelection);
4125            case SIGNALHEAD:
4126                return Conditional.Action.getSignalHeadItems().get(actionTypeSelection);
4127            case SIGNALMAST:
4128                return Conditional.Action.getSignalMastItems().get(actionTypeSelection);
4129            case MEMORY:
4130                return Conditional.Action.getMemoryItems().get(actionTypeSelection);
4131            case LOGIX:
4132                return Conditional.Action.getLogixItems().get(actionTypeSelection);
4133            case WARRANT:
4134                return Conditional.Action.getWarrantItems().get(actionTypeSelection);
4135            case OBLOCK:
4136                return Conditional.Action.getOBlockItems().get(actionTypeSelection);
4137            case CLOCK:
4138                return Conditional.Action.getClockItems().get(actionTypeSelection);
4139            case AUDIO:
4140                return Conditional.Action.getAudioItems().get(actionTypeSelection);
4141            case SCRIPT:
4142                return Conditional.Action.getScriptItems().get(actionTypeSelection);
4143            case OTHER:
4144                return Conditional.Action.getOtherItems().get(actionTypeSelection);
4145            case ENTRYEXIT:
4146                return Conditional.Action.getEntryExitItems().get(actionTypeSelection);
4147            default:
4148                // fall through
4149                break;
4150        }
4151        return Conditional.Action.NONE;
4152    }
4153*/
4154    JFileChooser sndFileChooser = null;
4155    ScriptFileChooser scriptFileChooser = null;
4156    JFileChooser defaultFileChooser = null;
4157
4158    /**
4159     * Respond to the [...] button in the Edit Action window action section.
4160     * <p>
4161     * Ask user to select an audio or python script file on disk.
4162     *
4163     * @param e the event heard
4164     */
4165    void setFileLocation(ActionEvent e) {
4166        ConditionalAction action = _actionList.get(_curNodeRow);
4167        JFileChooser currentChooser;
4168        Conditional.Action actionType = action.getType();
4169        if (actionType == Conditional.Action.PLAY_SOUND) {
4170            if (sndFileChooser == null) {
4171                sndFileChooser = new jmri.util.swing.JmriJFileChooser(System.getProperty("user.dir") // NOI18N
4172                        + java.io.File.separator + "resources" // NOI18N
4173                        + java.io.File.separator + "sounds");  // NOI18N
4174                sndFileChooser.setFileFilter(new FileNameExtensionFilter("wav sound files", "wav")); // NOI18N
4175            }
4176            currentChooser = sndFileChooser;
4177        } else if (actionType == Conditional.Action.RUN_SCRIPT) {
4178            if (scriptFileChooser == null) {
4179                scriptFileChooser = new ScriptFileChooser(FileUtil.getScriptsPath());
4180            }
4181            currentChooser = scriptFileChooser;
4182        } else {
4183            log.warn("Unexpected actionType[{}] = {}", actionType.name(), actionType);  // NOI18N
4184            if (defaultFileChooser == null) {
4185                defaultFileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
4186                defaultFileChooser.setFileFilter(new jmri.util.NoArchiveFileFilter());
4187            }
4188            currentChooser = defaultFileChooser;
4189        }
4190
4191        currentChooser.rescanCurrentDirectory();
4192        int retVal = currentChooser.showOpenDialog(null);
4193        // handle selection or cancel
4194        if (retVal == JFileChooser.APPROVE_OPTION) {
4195            // set selected file location in data string
4196            try {
4197                _longActionString.setText(FileUtil.getPortableFilename(currentChooser.getSelectedFile().getCanonicalPath()));
4198            } catch (java.io.IOException ex) {
4199                if (log.isDebugEnabled()) {
4200                    log.error("exception setting file location: ", ex);  // NOI18N
4201                }
4202                _longActionString.setText("");
4203            }
4204        }
4205    }
4206
4207    // ------------ Action update processes ------------
4208
4209    /**
4210     * Validate Action data from Edit Action Window, and transfer it to current
4211     * action object as appropriate.
4212     * <p>
4213     * Messages are sent to the user for any errors found. This routine returns
4214     * false immediately after finding an error, even if there might be more
4215     * errors.
4216     *
4217     * @return true if all data checks out OK, otherwise false.
4218     */
4219    boolean validateAction() {
4220        Conditional.ItemType itemType = _actionItemBox.getItemAt(_actionItemBox.getSelectedIndex());
4221        Conditional.Action actionType = Conditional.Action.NONE;
4222        Conditional.Action selection = _actionTypeBox.getItemAt(_actionTypeBox.getSelectedIndex());
4223        if (selection == Conditional.Action.NONE) {
4224           JmriJOptionPane.showMessageDialog(
4225                    _editLogixFrame, Bundle.getMessage("makeSelection"),
4226                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
4227            return false;
4228        }
4229        String name = _actionNameField.getText().trim();
4230        String actionString = _shortActionString.getText().trim();
4231        _curAction.setActionString("");
4232        _curAction.setActionData(-1);
4233        boolean referenceByMemory = false;
4234        if (name.length() > 0 && name.charAt(0) == '@') {
4235            String memName = name.substring(1);
4236            if (!confirmIndirectMemory(memName)) {
4237                return false;
4238            }
4239            memName = validateMemoryReference(memName);
4240            if (memName == null) {
4241                return false;
4242            }
4243            referenceByMemory = true;
4244        }
4245        if (!checkIsVariable(name, itemType) ) {
4246            return false;
4247        }
4248        switch (itemType) {
4249            case SENSOR:
4250                if (!referenceByMemory) {
4251                    name = validateSensorReference(name);
4252                    if (name == null) {
4253                        return false;
4254                    }
4255                }
4256                actionType = selection;
4257                if ((actionType == Conditional.Action.RESET_DELAYED_SENSOR)
4258                        || (actionType == Conditional.Action.DELAYED_SENSOR)) {
4259                    if (!validateTimeReference(actionType, actionString)) {
4260                        return (false);
4261                    }
4262                    _curAction.setActionString(actionString);
4263                }
4264                if ((actionType == Conditional.Action.SET_SENSOR)
4265                        || (actionType == Conditional.Action.RESET_DELAYED_SENSOR)
4266                        || (actionType == Conditional.Action.DELAYED_SENSOR)) {
4267                    if (_actionBox.getSelectedIndex() == 0) {
4268                        _curAction.setActionData(Sensor.ACTIVE);
4269                    } else if (_actionBox.getSelectedIndex() == 1) {
4270                        _curAction.setActionData(Sensor.INACTIVE);
4271                    } else {
4272                        _curAction.setActionData(Route.TOGGLE);
4273                    }
4274                }
4275                _actionNameField.setText(name);
4276                _curAction.setDeviceName(name);
4277                break;
4278            case TURNOUT:
4279                if (!referenceByMemory) {
4280                    name = validateTurnoutReference(name);
4281                    if (name == null) {
4282                        return false;
4283                    }
4284                }
4285                actionType = selection;
4286                if ((actionType == Conditional.Action.RESET_DELAYED_TURNOUT)
4287                        || (actionType == Conditional.Action.DELAYED_TURNOUT)) {
4288                    if (!validateTimeReference(actionType, actionString)) {
4289                        return (false);
4290                    }
4291                    _curAction.setActionString(actionString);
4292                }
4293                if ((actionType == Conditional.Action.SET_TURNOUT)
4294                        || (actionType == Conditional.Action.RESET_DELAYED_TURNOUT)
4295                        || (actionType == Conditional.Action.DELAYED_TURNOUT)) {
4296                    if (_actionBox.getSelectedIndex() == 0) {
4297                        _curAction.setActionData(Turnout.CLOSED);
4298                    } else if (_actionBox.getSelectedIndex() == 1) {
4299                        _curAction.setActionData(Turnout.THROWN);
4300                    } else {
4301                        _curAction.setActionData(Route.TOGGLE);
4302                    }
4303                } else if (actionType == Conditional.Action.LOCK_TURNOUT) {
4304                    if (_actionBox.getSelectedIndex() == 0) {
4305                        _curAction.setActionData(Turnout.UNLOCKED);
4306                    } else if (_actionBox.getSelectedIndex() == 1) {
4307                        _curAction.setActionData(Turnout.LOCKED);
4308                    } else {
4309                        _curAction.setActionData(Route.TOGGLE);
4310                    }
4311                }
4312                _actionNameField.setText(name);
4313                _curAction.setDeviceName(name);
4314                break;
4315            case LIGHT:
4316                if (!referenceByMemory) {
4317                    name = validateLightReference(name);
4318                    if (name == null) {
4319                        return false;
4320                    }
4321                }
4322                actionType = selection;
4323                if (actionType == Conditional.Action.SET_LIGHT_INTENSITY) {
4324                    Light lgtx = getLight(name);
4325                    // check if light user name was entered
4326                    if (lgtx == null) {
4327                        return false;
4328                    }
4329                    if (!(lgtx instanceof VariableLight)) {
4330                       JmriJOptionPane.showMessageDialog(_editLogixFrame,
4331                                Bundle.getMessage("Error45", name), // NOI18N
4332                                Bundle.getMessage("ErrorTitle"),
4333                                JmriJOptionPane.ERROR_MESSAGE);  // NOI18N
4334                        return (false);
4335                    }
4336                    if (!validateIntensityReference(actionType, actionString)) {
4337                        return (false);
4338                    }
4339                    _curAction.setActionString(actionString);
4340                } else if (actionType == Conditional.Action.SET_LIGHT_TRANSITION_TIME) {
4341                    Light lgtx = getLight(name);
4342                    // check if light user name was entered
4343                    if (lgtx == null) {
4344                        return false;
4345                    }
4346                    if ( !(lgtx instanceof VariableLight)
4347                            || !((VariableLight)lgtx).isTransitionAvailable()) {
4348                       JmriJOptionPane.showMessageDialog(_editLogixFrame,
4349                                Bundle.getMessage("Error40", name), // NOI18N
4350                                Bundle.getMessage("ErrorTitle"),
4351                                JmriJOptionPane.ERROR_MESSAGE);  // NOI18N
4352                        return (false);
4353                    }
4354                    if (!validateTimeReference(actionType, actionString)) {
4355                        return (false);
4356                    }
4357                    _curAction.setActionString(actionString);
4358                } else if (actionType == Conditional.Action.SET_LIGHT) {
4359                    if (_actionBox.getSelectedIndex() == 0) {
4360                        _curAction.setActionData(Light.ON);
4361                    } else if (_actionBox.getSelectedIndex() == 1) {
4362                        _curAction.setActionData(Light.OFF);
4363                    } else {
4364                        _curAction.setActionData(Route.TOGGLE);
4365                    }
4366                }
4367                _actionNameField.setText(name);
4368                _curAction.setDeviceName(name);
4369                break;
4370            case SIGNALHEAD:
4371                if (!referenceByMemory) {
4372                    name = validateSignalHeadReference(name);
4373                    if (name == null) {
4374                        return false;
4375                    }
4376                }
4377                actionType = selection;
4378                if (actionType == Conditional.Action.SET_SIGNAL_APPEARANCE) {
4379                    String appStr = (String) _actionBox.getSelectedItem();
4380                    _curAction.setActionData(DefaultConditionalAction.stringToActionData(appStr));
4381                    _curAction.setActionString(appStr);
4382                }
4383                _actionNameField.setText(name);
4384                _curAction.setDeviceName(name);
4385                break;
4386            case SIGNALMAST:
4387                if (!referenceByMemory) {
4388                    name = validateSignalMastReference(name);
4389                    if (name == null) {
4390                        return false;
4391                    }
4392                }
4393                actionType = selection;
4394                if (actionType == Conditional.Action.SET_SIGNALMAST_ASPECT) {
4395                    _curAction.setActionString((String) _actionBox.getSelectedItem());
4396                }
4397                _actionNameField.setText(name);
4398                _curAction.setDeviceName(name);
4399                break;
4400            case MEMORY:
4401                if (referenceByMemory) {
4402                   JmriJOptionPane.showMessageDialog(_editLogixFrame, Bundle.getMessage("Warn6"), Bundle.getMessage("WarningTitle"), // NOI18N
4403                            JmriJOptionPane.WARNING_MESSAGE);
4404                    return false;
4405                }
4406                name = validateMemoryReference(name);
4407                if (name == null) {
4408                    return false;
4409                }
4410                actionType = selection;
4411                if (actionType == Conditional.Action.COPY_MEMORY) {
4412                    actionString = validateMemoryReference(actionString);
4413                    if (actionString == null) {
4414                        return false;
4415                    }
4416                }
4417                _actionNameField.setText(name);
4418                _curAction.setDeviceName(name);
4419                _curAction.setActionString(actionString);
4420                break;
4421            case LOGIX:
4422                if (!referenceByMemory) {
4423                    name = validateLogixReference(name);
4424                    if (name == null) {
4425                        return false;
4426                    }
4427                }
4428                actionType = selection;
4429                _actionNameField.setText(name);
4430                _curAction.setDeviceName(name);
4431                break;
4432            case WARRANT:
4433                if (!referenceByMemory) {
4434                    name = validateWarrantReference(name);
4435                    if (name == null) {
4436                        return false;
4437                    }
4438                }
4439                actionType = selection;
4440                _actionNameField.setText(name);
4441                _curAction.setDeviceName(name);
4442                if (actionType == Conditional.Action.CONTROL_TRAIN) {
4443                    switch (_actionBox.getSelectedIndex()) {
4444                        case 0:
4445                            _curAction.setActionData(Warrant.HALT);
4446                            break;
4447                        case 1:
4448                            _curAction.setActionData(Warrant.RESUME);
4449                            break;
4450                        case 2:
4451                            _curAction.setActionData(Warrant.RETRY_FWD);
4452                            break;
4453                        case 3:
4454                            _curAction.setActionData(Warrant.SPEED_UP);
4455                            break;
4456                        case 4:
4457                            _curAction.setActionData(Warrant.STOP);
4458                            break;
4459                        case 5:
4460                            _curAction.setActionData(Warrant.ESTOP);
4461                            break;
4462                        case 6:
4463                            _curAction.setActionData(Warrant.ABORT);
4464                            break;
4465                        default:
4466                            log.warn("Unexpected _actionBox.getSelectedIndex() of {}", _actionBox.getSelectedIndex());  // NOI18N
4467                            break;
4468                    }
4469                } else if (actionType == Conditional.Action.SET_TRAIN_ID
4470                        || actionType == Conditional.Action.SET_TRAIN_NAME
4471                        || actionType == Conditional.Action.GET_TRAIN_LOCATION) {
4472                    _curAction.setActionString(actionString);
4473                }
4474                break;
4475            case OBLOCK:
4476                if (!referenceByMemory) {
4477                    name = validateOBlockReference(name);
4478                    if (name == null) {
4479                        return false;
4480                    }
4481                }
4482                actionType = selection;
4483                _actionNameField.setText(name);
4484                _curAction.setDeviceName(name);
4485                if (actionType == Conditional.Action.SET_BLOCK_VALUE
4486                        || actionType == Conditional.Action.SET_TRAIN_NAME
4487                        || actionType == Conditional.Action.GET_TRAIN_LOCATION) {
4488                    _curAction.setActionString(actionString);
4489                }
4490                break;
4491            case ENTRYEXIT:
4492                if (!referenceByMemory) {
4493                    name = validateEntryExitReference(name);
4494                    if (name == null) {
4495                        return false;
4496                    }
4497                }
4498                actionType = selection;
4499                _actionNameField.setText(name);
4500                _curAction.setDeviceName(name);
4501                break;
4502            case CLOCK:
4503                actionType = selection;
4504                if (actionType == Conditional.Action.SET_FAST_CLOCK_TIME) {
4505                    int time = parseTime(_shortActionString.getText().trim());
4506                    if (time < 0) {
4507                        return (false);
4508                    }
4509                    _curAction.setActionData(time);
4510                }
4511                break;
4512            case AUDIO:
4513                actionType = selection;
4514                if (actionType == Conditional.Action.PLAY_SOUND) {
4515                    _curAction.setActionString(_longActionString.getText().trim());
4516                } else if (actionType == Conditional.Action.CONTROL_AUDIO) {
4517                    if (!referenceByMemory) {
4518                        name = validateAudioReference(name);
4519                        if (name == null) {
4520                            return false;
4521                        }
4522                    }
4523                    _actionNameField.setText(name);
4524                    _curAction.setDeviceName(name);
4525                    switch (_actionBox.getSelectedIndex()) {
4526                        case 0:
4527                            _curAction.setActionData(Audio.CMD_PLAY);
4528                            break;
4529                        case 1:
4530                            _curAction.setActionData(Audio.CMD_STOP);
4531                            break;
4532                        case 2:
4533                            _curAction.setActionData(Audio.CMD_PLAY_TOGGLE);
4534                            break;
4535                        case 3:
4536                            _curAction.setActionData(Audio.CMD_PAUSE);
4537                            break;
4538                        case 4:
4539                            _curAction.setActionData(Audio.CMD_RESUME);
4540                            break;
4541                        case 5:
4542                            _curAction.setActionData(Audio.CMD_PAUSE_TOGGLE);
4543                            break;
4544                        case 6:
4545                            _curAction.setActionData(Audio.CMD_REWIND);
4546                            break;
4547                        case 7:
4548                            _curAction.setActionData(Audio.CMD_FADE_IN);
4549                            break;
4550                        case 8:
4551                            _curAction.setActionData(Audio.CMD_FADE_OUT);
4552                            break;
4553                        case 9:
4554                            _curAction.setActionData(Audio.CMD_RESET_POSITION);
4555                            break;
4556                        default:
4557                            log.warn("Unexpected _actionBox.getSelectedIndex() of {}", _actionBox.getSelectedIndex());  // NOI18N
4558                            break;
4559                    }
4560                }
4561                break;
4562            case SCRIPT:
4563                actionType = selection;
4564                if (actionType == Conditional.Action.RUN_SCRIPT) {
4565                    _curAction.setActionString(_longActionString.getText().trim());
4566                } else if (actionType == Conditional.Action.JYTHON_COMMAND) {
4567                    _curAction.setActionString(_shortActionString.getText().trim());
4568                }
4569                break;
4570            case OTHER:
4571                actionType = selection;
4572                if (actionType == Conditional.Action.TRIGGER_ROUTE) {
4573                    if (!referenceByMemory) {
4574                        name = validateRouteReference(name);
4575                        if (name == null) {
4576                            return false;
4577                        }
4578                    }
4579                    _actionNameField.setText(name);
4580                    _curAction.setDeviceName(name);
4581                }
4582                break;
4583            default:
4584                break;
4585        }
4586        _curAction.setType(actionType);
4587        if (actionType != Conditional.Action.NONE) {
4588            _curAction.setOption(_actionOptionBox.getSelectedIndex() + 1);
4589        } else {
4590            _curAction.setOption(0);
4591        }
4592        return (true);
4593    }
4594
4595    /**
4596     * Update the conditional action list and refresh the local copy.
4597     * The parent Logix is de-activated and re-activated.  This ensures
4598     * that listeners are properly handled, specifically the delayed sensor
4599     * and turnout options.
4600     * @since 4.11.2
4601     */
4602    void updateActionList() {
4603        _curLogix.deActivateLogix();
4604        _curConditional.setAction(_actionList);
4605        _actionList = _curConditional.getCopyOfActions();
4606        _curLogix.activateLogix();
4607    }
4608
4609    /**
4610     * Check that a state variable is not also used as an action
4611     *
4612     * @param name of the state variable
4613     * @param itemType item type of the state variable
4614     * @return true if variable is not an action of if the user OK's
4615     * its use as an action also.
4616     */
4617    boolean checkIsAction(String name, Conditional.ItemType itemType) {
4618        String actionName = null;
4619        for (ConditionalAction action : _actionList) {
4620            Conditional.ItemType actionType = action.getType().getItemType();
4621            if (itemType == actionType) {
4622                if (name.equals(action.getDeviceName())) {
4623                    actionName = action.getDeviceName();
4624                } else {
4625                    NamedBean bean  = action.getBean();
4626                    if (bean != null &&
4627                        (name.equals(bean.getSystemName()) ||
4628                                name.equals(bean.getUserName()))) {
4629                        actionName = action.getDeviceName();
4630                   }
4631                }
4632            }
4633            if (actionName != null) {
4634                return confirmActionAsVariable(actionName, name);
4635            }
4636        }
4637        return true;
4638    }
4639
4640    /**
4641     * Check that an action is not also used as a state variable
4642     *
4643     * @param name of the action
4644     * @param itemType item type of the action
4645     * @return true if action is not a state variable of if the user OK's
4646     * its use as such.
4647     */
4648    boolean checkIsVariable(String name, Conditional.ItemType itemType) {
4649        String varName = null;
4650        for (ConditionalVariable var : _variableList) {
4651            Conditional.ItemType varType = var.getType().getItemType();
4652            if (itemType == varType) {
4653                if (name.equals(var.getName())) {
4654                    varName = var.getName();
4655                } else {
4656                    NamedBean bean  = var.getBean();
4657                    if (bean != null &&
4658                        (name.equals(bean.getSystemName()) ||
4659                                name.equals(bean.getUserName()))) {
4660                        varName = var.getName();
4661                   }
4662                }
4663            }
4664            if (varName != null) {
4665                return confirmActionAsVariable(name, varName);
4666            }
4667        }
4668        return true;
4669    }
4670
4671    // ------------ Action detail listeners ------------
4672
4673    /**
4674     * Listener for _actionTypeBox.
4675     */
4676    class ActionTypeListener implements ActionListener {
4677
4678        Conditional.ItemType _itemType;
4679
4680        @Override
4681        public void actionPerformed(ActionEvent e) {
4682            Conditional.ItemType select1 = _actionItemBox.getItemAt(_actionItemBox.getSelectedIndex());
4683            Conditional.Action select2 = _actionTypeBox.getItemAt(_actionTypeBox.getSelectedIndex());
4684            if (log.isDebugEnabled()) {
4685                log.debug("ActionTypeListener: itemType = {}, local itemType = {}, actionType = {}",  // NOI18N
4686                        select1, _itemType, select2);
4687            }
4688            if (select1 != _itemType) {
4689                log.error("ActionTypeListener actionItem selection ({}) != expected actionItem ({})",  // NOI18N
4690                        select1, _itemType);
4691            }
4692            if (_curAction != null) {
4693                if (select1 != Conditional.ItemType.NONE && _itemType == select1) {
4694                    _curAction.setType(select2);
4695                    if (select1 == _itemType) {
4696                        String text = _actionNameField.getText();
4697                        if (text != null && text.length() > 0) {
4698                            _curAction.setDeviceName(text);
4699                        }
4700                    }
4701                    actionItemChanged(_itemType);
4702                    initializeActionVariables();
4703                }
4704            }
4705        }
4706
4707        public void setItemType(Conditional.ItemType type) {
4708            _itemType = type;
4709        }
4710    }
4711
4712    ActionTypeListener _actionTypeListener = new ActionTypeListener();
4713
4714    transient ActionListener actionSignalHeadNameListener = new ActionListener() {
4715        @Override
4716        public void actionPerformed(ActionEvent e) {
4717            // fired when signal head name changes, but only
4718            // while in signal head mode
4719            log.debug("actionSignalHeadNameListener fires; _actionNameField : {}", _actionNameField.getText().trim());  // NOI18N
4720            loadJComboBoxWithHeadAppearances(_actionBox, _actionNameField.getText().trim());
4721        }
4722    };
4723
4724    transient ActionListener actionSignalMastNameListener = new ActionListener() {
4725        @Override
4726        public void actionPerformed(ActionEvent e) {
4727            // fired when signal mast name changes, but only
4728            // while in signal mast mode
4729            log.debug("actionSignalMastNameListener fires; _actionNameField : {}", _actionNameField.getText().trim());  // NOI18N
4730            loadJComboBoxWithMastAspects(_actionBox, _actionNameField.getText().trim());
4731        }
4732    };
4733
4734    /**
4735     * Conditional Tree Node Definition.
4736     */
4737    static class ConditionalTreeNode extends DefaultMutableTreeNode {
4738
4739        private String cdlText;
4740        private String cdlType;
4741        private String cdlName;
4742        private int cdlRow;
4743
4744        public ConditionalTreeNode(String nameText, String type, String sysName, int row) {
4745            this.cdlText = nameText;
4746            this.cdlType = type;
4747            this.cdlName = sysName;
4748            this.cdlRow = row;
4749        }
4750
4751        public String getType() {
4752            return cdlType;
4753        }
4754
4755        public String getName() {
4756            return cdlName;
4757        }
4758
4759        public void setName(String newName) {
4760            cdlName = newName;
4761        }
4762
4763        public int getRow() {
4764            return cdlRow;
4765        }
4766
4767        public void setRow(int newRow) {
4768            cdlRow = newRow;
4769        }
4770
4771        public String getText() {
4772            return cdlText;
4773        }
4774
4775        public void setText(String newText) {
4776            cdlText = newText;
4777        }
4778
4779        @Override
4780        public String toString() {
4781            return cdlText;
4782        }
4783    }
4784
4785    @Override
4786    protected String getClassName() {
4787        return ConditionalTreeEdit.class.getName();
4788    }
4789
4790    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConditionalTreeEdit.class);
4791
4792}