001package jmri.jmrit.conditional;
002
003import java.awt.Component;
004import java.awt.Container;
005import java.awt.event.ActionEvent;
006import java.awt.event.ActionListener;
007import java.util.ArrayList;
008import java.util.EventListener;
009import java.util.HashMap;
010import java.util.List;
011import java.util.TreeSet;
012
013import javax.annotation.CheckForNull;
014import javax.annotation.Nonnull;
015
016import javax.swing.JFrame;
017import javax.swing.JTabbedPane;
018import javax.swing.JTable;
019import javax.swing.JTextField;
020import javax.swing.event.ListSelectionEvent;
021import javax.swing.event.ListSelectionListener;
022
023import jmri.Audio;
024import jmri.Conditional;
025import jmri.ConditionalManager;
026import jmri.ConditionalVariable;
027import jmri.InstanceManager;
028import jmri.Light;
029import jmri.LightManager;
030import jmri.Logix;
031import jmri.LogixManager;
032import jmri.Memory;
033import jmri.MemoryManager;
034import jmri.NamedBean;
035import jmri.Route;
036import jmri.Sensor;
037import jmri.SensorManager;
038import jmri.SignalHead;
039import jmri.SignalHeadManager;
040import jmri.SignalMast;
041import jmri.SignalMastManager;
042import jmri.Turnout;
043import jmri.TurnoutManager;
044import jmri.NamedBean.DisplayOptions;
045import jmri.jmrit.beantable.LRouteTableAction;
046import jmri.jmrit.entryexit.DestinationPoints;
047import jmri.jmrit.entryexit.EntryExitPairs;
048import jmri.jmrit.logix.OBlock;
049import jmri.jmrit.logix.OBlockManager;
050import jmri.jmrit.logix.Warrant;
051import jmri.jmrit.logix.WarrantManager;
052import jmri.jmrit.picker.PickFrame;
053import jmri.jmrit.picker.PickListModel;
054import jmri.jmrit.picker.PickSinglePanel;
055import jmri.swing.NamedBeanComboBox;
056import jmri.util.JmriJFrame;
057import jmri.util.swing.JComboBoxUtil;
058import jmri.util.swing.JmriJOptionPane;
059
060/**
061 * This is the base class for the Conditional edit view classes. Contains shared
062 * variables and methods.
063 *
064 * @author Dave Sand copyright (c) 2017
065 */
066public class ConditionalEditBase {
067
068    /**
069     * Set the Logix and Conditional managers and set the selection mode.
070     *
071     * @param sName the Logix system name being edited
072     */
073    public ConditionalEditBase(String sName) {
074//         _logixManager = InstanceManager.getNullableDefault(jmri.LogixManager.class);
075//         _conditionalManager = InstanceManager.getNullableDefault(jmri.ConditionalManager.class);
076        _logixManager = InstanceManager.getDefault(jmri.LogixManager.class);
077        _conditionalManager = InstanceManager.getDefault(jmri.ConditionalManager.class);
078        _curLogix = _logixManager.getBySystemName(sName);
079        loadSelectionMode();
080    }
081
082    public ConditionalEditBase() {
083    }
084
085    // ------------ variable definitions ------------
086    ConditionalManager _conditionalManager = null;
087    LogixManager _logixManager = null;
088    Logix _curLogix = null;
089    JmriJFrame _editLogixFrame = null;
090
091    boolean _inEditMode = false;
092
093    boolean _showReminder = false;
094    boolean _suppressReminder = false;
095    boolean _suppressIndirectRef = false;
096    private boolean _checkEnabled = jmri.InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).isStoreCheckEnabled();
097
098    /**
099     * Input selection names.
100     *
101     * @since 4.7.3
102     */
103    public enum SelectionMode {
104        /**
105         * Use the traditional text field, with the tabbed Pick List available
106         * for drag-n-drop
107         */
108        USEMULTI,
109        /**
110         * Use the traditional text field, but with a single Pick List that
111         * responds with a click
112         */
113        USESINGLE,
114        /**
115         * Use combo boxes to select names instead of a text field.
116         */
117        USECOMBO;
118    }
119    SelectionMode _selectionMode;
120
121    /**
122     * Get the saved mode selection, default to the tranditional tabbed pick
123     * list.
124     * <p>
125     * During the menu build process, the corresponding menu item is set to
126     * selected.
127     *
128     * @since 4.7.3
129     */
130    void loadSelectionMode() {
131        Object modeName = InstanceManager.getDefault(jmri.UserPreferencesManager.class).getProperty("jmri.jmrit.beantable.LogixTableAction", "Selection Mode"); // NOI18N
132        if (modeName == null) {
133            _selectionMode = SelectionMode.USEMULTI;
134        } else {
135            String currentMode = (String) modeName;
136            switch (currentMode) {
137                case "USEMULTI":        // NOI18N
138                    _selectionMode = SelectionMode.USEMULTI;
139                    break;
140                case "USESINGLE":       // NOI18N
141                    _selectionMode = SelectionMode.USESINGLE;
142                    break;
143                case "USECOMBO":        // NOI18N
144                    _selectionMode = SelectionMode.USECOMBO;
145                    break;
146                default:
147                    log.warn("Invalid Logix conditional selection mode value, '{}', returned", currentMode);  // NOI18N
148                    _selectionMode = SelectionMode.USEMULTI;
149            }
150        }
151    }
152
153    // ------------ PickList components ------------
154    JTable _pickTable = null;               // Current pick table
155    JTabbedPane _pickTabPane = null;        // The tabbed panel for the pick table
156    PickFrame _pickTables;
157
158    JFrame _pickSingleFrame = null;
159    PickSingleListener _pickListener = null;
160
161    // ------------ Logix Notifications ------------
162    // The Conditional views support some direct changes to the parent logix.
163    // This custom event is used to notify the parent Logix that changes are requested.
164    // When the event occurs, the parent Logix can retrieve the necessary information
165    // to carry out the actions.
166    //
167    // 1) Notify the calling Logix that the Logix user name has been changed.
168    // 2) Notify the calling Logix that the conditional view is closing
169    // 3) Notify the calling Logix that it is to be deleted
170    /**
171     * Create a custom listener event.
172     */
173    public interface LogixEventListener extends EventListener {
174
175        void logixEventOccurred();
176    }
177
178    /**
179     * Maintain a list of listeners -- normally only one.
180     */
181    List<LogixEventListener> listenerList = new ArrayList<>();
182
183    /**
184     * This contains a list of commands to be processed by the listener
185     * recipient.
186     */
187    public HashMap<String, String> logixData = new HashMap<>();
188
189    /**
190     * Add a listener.
191     *
192     * @param listener The recipient
193     */
194    public void addLogixEventListener(LogixEventListener listener) {
195        listenerList.add(listener);
196    }
197
198    /**
199     * Remove a listener -- not used.
200     *
201     * @param listener The recipient
202     */
203    public void removeLogixEventListener(LogixEventListener listener) {
204        listenerList.remove(listener);
205    }
206
207    /**
208     * Notify the listeners to check for new data.
209     */
210    void fireLogixEvent() {
211        for (LogixEventListener l : listenerList) {
212            l.logixEventOccurred();
213        }
214    }
215
216    // ------------ Antecedent Methods ------------
217
218    /**
219     * Create an antecedent string based on the current variables
220     * <p>
221     * The antecedent consists of all of the variables "in order"
222     * combined with the current operator.
223     * @since 4.11.5
224     * @param variableList The current variable list
225     * @return the resulting antecedent string
226     */
227    String makeAntecedent(List<ConditionalVariable> variableList) {
228        StringBuilder antecedent = new StringBuilder(64);
229        if (!variableList.isEmpty()) {
230            String row = "R"; // NOI18N
231            if (variableList.get(0).isNegated()) {
232                antecedent.append("not ");
233            }
234            antecedent.append(row + "1");
235            for (int i = 1; i < variableList.size(); i++) {
236                ConditionalVariable variable = variableList.get(i);
237                switch (variable.getOpern()) {
238                    case AND:
239                        antecedent.append(" and ");
240                        break;
241                    case OR:
242                        antecedent.append(" or ");
243                        break;
244                    default:
245                        break;
246                }
247                if (variable.isNegated()) {
248                    antecedent = antecedent.append("not ");
249                }
250                antecedent.append(row);
251                antecedent.append(i + 1);
252            }
253        }
254        return antecedent.toString();
255    }
256
257    /**
258     * Add a variable R# entry to the antecedent string.
259     * If not the first one, include <strong>and</strong> or <strong>or</strong> depending on the logic type
260     * @since 4.11.5
261     * @param logicType The current logic type.
262     * @param varListSize The current size of the variable list.
263     * @param antecedent The current antecedent
264     * @return an extended antecedent
265     */
266    String appendToAntecedent(Conditional.AntecedentOperator logicType, int varListSize, String antecedent) {
267        if (varListSize > 1) {
268            if (logicType == Conditional.AntecedentOperator.ALL_OR) {
269                antecedent = antecedent + " or ";   // NOI18N
270            } else {
271                antecedent = antecedent + " and ";  // NOI18N
272            }
273        }
274        return antecedent + "R" + varListSize; // NOI18N
275    }
276
277    /**
278     * Check the antecedent and logic type.
279     * <p>
280     * The antecedent text is translated and verified.  A new one is created if necessary.
281     * @since 4.11.5
282     * @param logicType The current logic type.  Types other than Mixed are ignored.
283     * @param antecedentText The proposed antecedent string using the local language.
284     * @param variableList The current variable list.
285     * @param curConditional The current conditional.
286     * @return false if antecedent can't be validated
287     */
288    boolean validateAntecedent(Conditional.AntecedentOperator logicType, String antecedentText, List<ConditionalVariable> variableList, Conditional curConditional) {
289        if (logicType != Conditional.AntecedentOperator.MIXED
290                || LRouteTableAction.getLogixInitializer().equals(_curLogix.getSystemName())
291                || antecedentText == null
292                || antecedentText.trim().length() == 0) {
293            return true;
294        }
295
296        String antecedent = translateAntecedent(antecedentText, true);
297        if (antecedent.length() > 0) {
298            String message = curConditional.validateAntecedent(antecedent, variableList);
299            if (message != null) {
300                JmriJOptionPane.showMessageDialog(_editLogixFrame,
301                        message + Bundle.getMessage("ParseError8"), // NOI18N
302                        Bundle.getMessage("ErrorTitle"),            // NOI18N
303                        JmriJOptionPane.ERROR_MESSAGE);
304                return false;
305            }
306        }
307        return true;
308    }
309
310    /**
311     * Translate an antecedent string between English and the current language
312     * as determined by the Bundle classes.
313     * <p>
314     * The property files have Logic??? keys for translating to the target language.
315     * @since 4.11.5
316     * @param antecedent The antecedent string which can either local or English
317     * @param isLocal True if the antecedent string has local words.
318     * @return the translated antecedent string.
319     */
320    public static String translateAntecedent(String antecedent, boolean isLocal) {
321        if (antecedent == null) {
322            return null;
323        }
324        String oldAnd, oldOr, oldNot;
325        String newAnd, newOr, newNot;
326        if (isLocal) {
327            // To English
328            oldAnd = Bundle.getMessage("LogicAND").toLowerCase();  // NOI18N
329            oldOr = Bundle.getMessage("LogicOR").toLowerCase();    // NOI18N
330            oldNot = Bundle.getMessage("LogicNOT").toLowerCase();  // NOI18N
331            newAnd = "and";  // NOI18N
332            newOr = "or";    // NOI18N
333            newNot = "not";  // NOI18N
334        } else {
335            // From English
336            oldAnd = "and";  // NOI18N
337            oldOr = "or";    // NOI18N
338            oldNot = "not";  // NOI18N
339            newAnd = Bundle.getMessage("LogicAND").toLowerCase();  // NOI18N
340            newOr = Bundle.getMessage("LogicOR").toLowerCase();    // NOI18N
341            newNot = Bundle.getMessage("LogicNOT").toLowerCase();  // NOI18N
342        }
343        log.debug("translateAntecedent: before {}", antecedent);
344        antecedent = antecedent.replaceAll(oldAnd, newAnd);
345        antecedent = antecedent.replaceAll(oldOr, newOr);
346        antecedent = antecedent.replaceAll(oldNot, newNot);
347        log.debug("translateAntecedent: after  {}", antecedent);
348        return antecedent;
349    }
350
351    // ------------ Shared Conditional Methods ------------
352
353    /**
354     * Verify that the user name is not a duplicate for the selected Logix.
355     *
356     * @param uName is the user name to be checked
357     * @param logix is the Logix that is being updated
358     * @return true if the name is unique
359     */
360    boolean checkConditionalUserName(String uName, Logix logix) {
361        if (uName != null && uName.length() > 0) {
362            Conditional p = _conditionalManager.getByUserName(logix, uName);
363            if (p != null) {
364                // Conditional with this user name already exists
365                log.error("Failure to update Conditional with Duplicate User Name: {}", uName);
366                JmriJOptionPane.showMessageDialog(_editLogixFrame,
367                        Bundle.getMessage("Error10"), // NOI18N
368                        Bundle.getMessage("ErrorTitle"), // NOI18N
369                        JmriJOptionPane.ERROR_MESSAGE);
370                return false;
371            }
372        } // else return true;
373        return true;
374    }
375
376    /**
377     * Create a combo name box for Variable and Action name selection.
378     *
379     * @param itemType The selected variable or action type
380     * @return nameBox A combo box based on the item type
381     */
382    NamedBeanComboBox<?> createNameBox(@Nonnull Conditional.ItemType itemType) {
383        NamedBeanComboBox<?> nameBox;
384        switch (itemType) {
385            case SENSOR:      // 1
386                nameBox = new NamedBeanComboBox<Sensor>(
387                        InstanceManager.getDefault(SensorManager.class), null, DisplayOptions.DISPLAYNAME);
388                break;
389            case TURNOUT:     // 2
390                nameBox = new NamedBeanComboBox<Turnout>(
391                        InstanceManager.getDefault(TurnoutManager.class), null, DisplayOptions.DISPLAYNAME);
392                break;
393            case LIGHT:       // 3
394                nameBox = new NamedBeanComboBox<Light>(
395                        InstanceManager.getDefault(LightManager.class), null, DisplayOptions.DISPLAYNAME);
396                break;
397            case SIGNALHEAD:  // 4
398                nameBox = new NamedBeanComboBox<SignalHead>(
399                        InstanceManager.getDefault(SignalHeadManager.class), null, DisplayOptions.DISPLAYNAME);
400                break;
401            case SIGNALMAST:  // 5
402                nameBox = new NamedBeanComboBox<SignalMast>(
403                        InstanceManager.getDefault(SignalMastManager.class), null, DisplayOptions.DISPLAYNAME);
404                break;
405            case MEMORY:      // 6
406                nameBox = new NamedBeanComboBox<Memory>(
407                        InstanceManager.getDefault(MemoryManager.class), null, DisplayOptions.DISPLAYNAME);
408                break;
409            case LOGIX:       // 7
410                nameBox = new NamedBeanComboBox<Logix>(
411                        InstanceManager.getDefault(LogixManager.class), null, DisplayOptions.DISPLAYNAME);
412                break;
413            case WARRANT:     // 8
414                nameBox = new NamedBeanComboBox<Warrant>(
415                        InstanceManager.getDefault(WarrantManager.class), null, DisplayOptions.DISPLAYNAME);
416                break;
417            case OBLOCK:      // 10
418                nameBox = new NamedBeanComboBox<OBlock>(
419                        InstanceManager.getDefault(OBlockManager.class), null, DisplayOptions.DISPLAYNAME);
420                break;
421            case ENTRYEXIT:   // 11
422                nameBox = new NamedBeanComboBox<DestinationPoints>(
423                        InstanceManager.getDefault(EntryExitPairs.class), null, DisplayOptions.DISPLAYNAME);
424                break;
425            case OTHER:   // 14
426                nameBox = new NamedBeanComboBox<Route>(
427                        InstanceManager.getDefault(jmri.RouteManager.class), null, DisplayOptions.DISPLAYNAME);
428                break;
429            default:
430                return null;             // Skip any other items.
431        }
432        nameBox.setAllowNull(true);
433        JComboBoxUtil.setupComboBoxMaxRows(nameBox);
434        return nameBox;
435    }
436
437    /**
438     * Listen for name combo box selection events.
439     * <p>
440     * When a combo box row is selected, the user/system name is copied to the
441     * Action or Variable name field.
442     *
443     * @since 4.7.3
444     */
445    static class NameBoxListener implements ActionListener {
446
447        /**
448         * @param textField The target field object when an entry is selected
449         */
450        NameBoxListener(JTextField textField) {
451            saveTextField = textField;
452        }
453
454        private final JTextField saveTextField;
455
456        @Override
457        public void actionPerformed(ActionEvent e) {
458            // Get the combo box and display name
459            Object src = e.getSource();
460            if (!(src instanceof NamedBeanComboBox)) {
461                return;
462            }
463            NamedBeanComboBox<?> srcBox = (NamedBeanComboBox<?>) src;
464            String newName = srcBox.getSelectedItemDisplayName();
465
466            log.debug("NameBoxListener: new name = '{}'", newName);  // NOI18N
467            saveTextField.setText(newName);
468        }
469    }
470
471    // ------------ Single Pick List Table Methods ------------
472
473    /**
474     * Create a single panel picklist JFrame for choosing action and variable
475     * names.
476     *
477     * @since 4.7.3
478     * @param itemType   The selected variable or action type
479     * @param listener   The listener to be assigned to the picklist
480     * @param actionType True if Action, false if Variable.
481     */
482    void createSinglePanelPickList(Conditional.ItemType itemType, PickSingleListener listener, boolean actionType) {
483        if (_pickListener != null) {
484            Conditional.ItemType saveType = _pickListener.getItemType();
485            if (saveType != itemType) {
486                // The type has changed, need to start over
487                closeSinglePanelPickList();
488            } else {
489                // The pick list has already been created
490                return;
491            }
492        }
493
494        PickSinglePanel<?> pickSingle;
495
496        switch (itemType) {
497            case SENSOR:      // 1
498                pickSingle = new PickSinglePanel<Sensor>(PickListModel.sensorPickModelInstance());
499                break;
500            case TURNOUT:     // 2
501                pickSingle = new PickSinglePanel<Turnout>(PickListModel.turnoutPickModelInstance());
502                break;
503            case LIGHT:       // 3
504                pickSingle = new PickSinglePanel<Light>(PickListModel.lightPickModelInstance());
505                break;
506            case SIGNALHEAD:  // 4
507                pickSingle = new PickSinglePanel<SignalHead>(PickListModel.signalHeadPickModelInstance());
508                break;
509            case SIGNALMAST:  // 5
510                pickSingle = new PickSinglePanel<SignalMast>(PickListModel.signalMastPickModelInstance());
511                break;
512            case MEMORY:      // 6
513                pickSingle = new PickSinglePanel<Memory>(PickListModel.memoryPickModelInstance());
514                break;
515            case LOGIX:      // 7 -- can be either Logix or Conditional
516                if (!actionType) {
517                    // State Variable
518                    return;
519                }
520                pickSingle = new PickSinglePanel<Logix>(PickListModel.logixPickModelInstance());
521                break;
522            case WARRANT:     // 8
523                pickSingle = new PickSinglePanel<Warrant>(PickListModel.warrantPickModelInstance());
524                break;
525            case OBLOCK:      // 10
526                pickSingle = new PickSinglePanel<OBlock>(PickListModel.oBlockPickModelInstance());
527                break;
528            case ENTRYEXIT:   // 11
529                pickSingle = new PickSinglePanel<jmri.jmrit.entryexit.DestinationPoints>(PickListModel.entryExitPickModelInstance());
530                break;
531            default:
532                return;             // Skip any other items.
533        }
534
535        // Create the JFrame
536        _pickSingleFrame = new JmriJFrame(Bundle.getMessage("SinglePickFrame"));  // NOI18N
537        _pickSingleFrame.setContentPane(pickSingle);
538        _pickSingleFrame.pack();
539        _pickSingleFrame.setVisible(true);
540        _pickSingleFrame.toFront();
541
542        // Set the table selection listener
543        _pickListener = listener;
544        _pickTable = pickSingle.getTable();
545        _pickTable.getSelectionModel().addListSelectionListener(_pickListener);
546    }
547
548    /**
549     * Close a single panel picklist JFrame and related items.
550     *
551     * @since 4.7.3
552     */
553    void closeSinglePanelPickList() {
554        if (_pickSingleFrame != null) {
555            _pickSingleFrame.setVisible(false);
556            _pickSingleFrame.dispose();
557            _pickSingleFrame = null;
558            _pickListener = null;
559            _pickTable = null;
560        }
561    }
562
563    /**
564     * Listen for Pick Single table click events.
565     * <p>
566     * When a table row is selected, the user/system name is copied to the
567     * Action or Variable name field.
568     *
569     * @since 4.7.3
570     */
571    class PickSingleListener implements ListSelectionListener {
572
573        /**
574         * @param textField The target field object when an entry is selected
575         * @param itemType  The current selected table type number
576         */
577        PickSingleListener(JTextField textField, Conditional.ItemType itemType) {
578            saveItemType = itemType;
579            saveTextField = textField;
580        }
581
582        private final JTextField saveTextField;
583        private final Conditional.ItemType saveItemType;          // Current table type
584
585        @Override
586        public void valueChanged(ListSelectionEvent e) {
587            int selectedRow = _pickTable.getSelectedRow();
588            if (selectedRow >= 0) {
589                int selectedCol = _pickTable.getSelectedColumn();
590                String newName = (String) _pickTable.getValueAt(selectedRow, selectedCol);
591                log.debug("Pick single panel row event: row = '{}', column = '{}', selected name = '{}'",
592                    selectedRow, selectedCol, newName);
593                saveTextField.setText(newName);
594            }
595        }
596
597        public Conditional.ItemType getItemType() {
598            return saveItemType;
599        }
600    }
601
602    // ------------ Pick List Table Methods ------------
603
604    /**
605     * Open a new drag-n-drop Pick List to drag Variable and Action names from
606     * to form Logix Conditionals.
607     */
608    void openPickListTable() {
609        if (_pickTables == null) {
610            _pickTables = new jmri.jmrit.picker.PickFrame(Bundle.getMessage("TitlePickList"));  // NOI18N
611        } else {
612            _pickTables.setVisible(true);
613        }
614        _pickTables.toFront();
615    }
616
617    /**
618     * Hide the drag-n-drop Pick List if the last detail edit is closing.
619     */
620    void hidePickListTable() {
621        if (_pickTables != null) {
622            _pickTables.setVisible(false);
623        }
624    }
625
626    /**
627     * Set the pick list tab based on the variable or action type. If there is
628     * not a corresponding tab, hide the picklist.
629     *
630     * @param curType    is the current type
631     * @param actionType True if Action, false if Variable.
632     */
633    void setPickListTab(Conditional.ItemType curType, boolean actionType) {
634        boolean tabSet = true;
635        if (_pickTables == null) {
636            return;
637        }
638        if (_pickTabPane == null) {
639            findPickListTabPane(_pickTables.getComponents(), 1);
640        }
641        if (_pickTabPane != null) {
642            // Convert variable/action type to the corresponding tab index
643            int tabIndex = 0;
644            switch (curType) {
645                case SENSOR:    // 1
646                    tabIndex = 1;
647                    break;
648                case TURNOUT:   // 2
649                    tabIndex = 0;
650                    break;
651                case LIGHT:     // 3
652                    tabIndex = 6;
653                    break;
654                case SIGNALHEAD:            // 4
655                    tabIndex = 2;
656                    break;
657                case SIGNALMAST:            // 5
658                    tabIndex = 3;
659                    break;
660                case MEMORY:    // 6
661                    tabIndex = 4;
662                    break;
663                case LOGIX:     // 7 Conditional (Variable) or Logix (Action)
664                    if (actionType) {
665                        tabIndex = 10;
666                    } else {
667                        // State Variable
668                        tabSet = false;
669                    }
670                    break;
671                case WARRANT:   // 8
672                    tabIndex = 7;
673                    break;
674                case OBLOCK:    // 10
675                    tabIndex = 8;
676                    break;
677                case ENTRYEXIT: // 11
678                    tabIndex = 9;
679                    break;
680                default:
681                    // No tab found
682                    tabSet = false;
683            }
684            if (tabSet) {
685                _pickTabPane.setSelectedIndex(tabIndex);
686            }
687        }
688        _pickTables.setVisible(tabSet);
689    }
690
691    /**
692     * Recursive search for the tab panel.
693     *
694     * @param compList The components for the current Level
695     * @param level    The current level in the structure
696     */
697    void findPickListTabPane(Component[] compList, int level) {
698        for (Component compItem : compList) {
699            // Safety catch
700            if (level > 10) {
701                log.warn("findPickListTabPane: safety breaker reached");  // NOI18N
702                return;
703            }
704
705            if (compItem instanceof JTabbedPane) {
706                _pickTabPane = (JTabbedPane) compItem;
707            } else {
708                int nextLevel = level + 1;
709                if ( compItem instanceof Container ) {
710                    Container nextItem = (Container) compItem;
711                    Component[] nextList = nextItem.getComponents();
712                    findPickListTabPane(nextList, nextLevel);
713                } else {
714                    log.error("compItem {} is not a JTabbedPane, nor Container", compItem);
715                }
716            }
717        }
718    }
719
720    // ------------ Manage Conditional Reference map ------------
721
722    /**
723     * Build a tree set from conditional references.
724     *
725     * @since 4.7.4
726     * @param varList The ConditionalVariable list that might contain
727     *                conditional references
728     * @param treeSet A tree set to be built from the varList data
729     */
730    void loadReferenceNames(List<ConditionalVariable> varList, TreeSet<String> treeSet) {
731        treeSet.clear();
732        for (ConditionalVariable condVar : varList) {
733            if (condVar.getType() == Conditional.Type.CONDITIONAL_TRUE
734                    || condVar.getType() == Conditional.Type.CONDITIONAL_FALSE) {
735                treeSet.add(condVar.getName());
736            }
737        }
738    }
739
740    /**
741     * Check for conditional references.
742     *
743     * @since 4.7.4
744     * @param logixName The Logix under consideration
745     * @return true if no references
746     */
747    boolean checkConditionalReferences(String logixName) {
748        Logix x = _logixManager.getLogix(logixName);
749        int numConditionals = x.getNumConditionals();
750        for (int i = 0; i < numConditionals; i++) {
751            String csName = x.getConditionalByNumberOrder(i);
752
753            // If the conditional is a where used target, check scope
754            ArrayList<String> refList = InstanceManager.getDefault(jmri.ConditionalManager.class).getWhereUsed(csName);
755            if (refList != null) {
756                for (String refName : refList) {
757                    Logix xRef = _conditionalManager.getParentLogix(refName);
758                    if (xRef == null) {
759                        log.error("Could not fetch Logix for ref: {}", refName);
760                        continue;
761                    }
762                    String xsName = xRef.getSystemName();
763                    if (logixName.equals(xsName)) {
764                        // Member of the same Logix
765                        continue;
766                    }
767
768                    // External references have to be removed before the Logix can be deleted.
769                    Conditional c = x.getConditional(csName);
770                    Conditional cRef = xRef.getConditional(refName);
771                    Object[] msgs = new Object[]{(c != null ? c.getUserName() : "NULL"), csName,
772                        ( cRef != null ? cRef.getUserName() : "NULL"), refName,
773                        xRef.getUserName(), xRef.getSystemName()};
774                    JmriJOptionPane.showMessageDialog(_editLogixFrame,
775                            Bundle.getMessage("Error11", msgs), // NOI18N
776                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); // NOI18N
777                    return false;
778                }
779            }
780        }
781        return true;
782    }
783
784    /**
785     * Update the conditional reference where used.
786     * <p>
787     * The difference between the saved target names and new target names is
788     * used to add/remove where used references.
789     *
790     * @since 4.7.4
791     * @param oldTargetNames The conditional target names before updating
792     * @param newTargetNames The conditional target names after updating
793     * @param refName        The system name for the referencing conditional
794     */
795    void updateWhereUsed(TreeSet<String> oldTargetNames, TreeSet<String> newTargetNames, String refName) {
796        TreeSet<String> deleteNames = new TreeSet<>(oldTargetNames);
797        deleteNames.removeAll(newTargetNames);
798        for (String deleteName : deleteNames) {
799            InstanceManager.getDefault(jmri.ConditionalManager.class).removeWhereUsed(deleteName, refName);
800        }
801
802        TreeSet<String> addNames = new TreeSet<>(newTargetNames);
803        addNames.removeAll(oldTargetNames);
804        for (String addName : addNames) {
805            InstanceManager.getDefault(jmri.ConditionalManager.class).addWhereUsed(addName, refName);
806        }
807    }
808
809    // ------------ Utility Methods - Data Validation ------------
810    /**
811     * Display reminder to save.  The class is set to LogixTableAction.
812     */
813    void showSaveReminder() {
814        jmri.UserPreferencesManager upm = InstanceManager.getNullableDefault(jmri.UserPreferencesManager.class);
815        if ( _showReminder && !_checkEnabled && upm != null ) {
816            upm.showInfoMessage(Bundle.getMessage("ReminderTitle"),
817                Bundle.getMessage("ReminderSaveString", Bundle.getMessage("MenuItemLogixTable")),
818                "jmri.jmrit.beantable.LogixTableAction","remindSaveLogix");
819        }
820    }
821
822    /**
823     * Check if String is an integer or references an integer.
824     *
825     * @param actionType   Conditional action to check for, i.e.
826     *                     ACTION_SET_LIGHT_INTENSITY
827     * @param intReference string referencing a decimal for light intensity or
828     *                     the name of a memory
829     * @return true if either correct decimal format or a memory with the given
830     *         name is present
831     */
832    boolean validateIntensityReference(Conditional.Action actionType, @CheckForNull String intReference) {
833        if (intReference == null || intReference.trim().length() == 0) {
834            displayBadNumberReference(actionType);
835            return false;
836        }
837        try {
838            return validateIntensity(Integer.parseInt(intReference));
839        } catch (NumberFormatException e) {
840            String intRef = intReference;
841            if (intReference.length() > 1 && intReference.charAt(0) == '@') {
842                intRef = intRef.substring(1);
843            }
844            if (!confirmIndirectMemory(intRef)) {
845                return false;
846            }
847            intRef = validateMemoryReference(intRef);
848            if (intRef != null) // memory named 'intReference' exists
849            {
850                Memory m = InstanceManager.memoryManagerInstance().getByUserName(intRef);
851                if (m == null) {
852                    m = InstanceManager.memoryManagerInstance().getBySystemName(intRef);
853                }
854                try {
855                    if (m == null || m.getValue() == null) {
856                        throw new NumberFormatException();
857                    }
858                    validateIntensity(Integer.parseInt((String) m.getValue()));
859                } catch (NumberFormatException ex) {
860                    JmriJOptionPane.showMessageDialog(_editLogixFrame,
861                            Bundle.getMessage("Error24", intReference),
862                            Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
863                }
864                return true;    // above is a warning to set memory correctly
865            }
866            displayBadNumberReference(actionType);
867        }
868        return false;
869    }
870
871    /**
872     * Check if text represents an integer is suitable for percentage w/o
873     * NumberFormatException.
874     *
875     * @param time value to use as light intensity percentage
876     * @return true if time is an integer in range 0 - 100
877     */
878    boolean validateIntensity(int time) {
879        if (time < 0 || time > 100) {
880            JmriJOptionPane.showMessageDialog(_editLogixFrame,
881                    Bundle.getMessage("Error38", time, Bundle.getMessage("Error42")),
882                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
883            return false;
884        }
885        return true;
886    }
887
888    /**
889     * Check if a string is decimal or references a decimal.
890     *
891     * @param actionType enum representing the Conditional action type being
892     *                   checked, i.e. ACTION_DELAYED_TURNOUT
893     * @param ref        entry to check
894     * @return true if ref is itself a decimal or user will provide one from a
895     *         Memory at run time
896     */
897    boolean validateTimeReference(Conditional.Action actionType, @CheckForNull String ref) {
898        if (ref == null || ref.trim().length() == 0) {
899            displayBadNumberReference(actionType);
900            return false;
901        }
902        try {
903            return validateTime(actionType, Float.parseFloat(ref));
904            // return true if ref is decimal within allowed range
905        } catch (NumberFormatException e) {
906            String memRef = ref;
907            if (ref.length() > 1 && ref.charAt(0) == '@') {
908                memRef = ref.substring(1);
909            }
910            if (!confirmIndirectMemory(memRef)) {
911                return false;
912            }
913            memRef = validateMemoryReference(memRef);
914            if (memRef != null) // memory named 'intReference' exists
915            {
916                Memory m = InstanceManager.memoryManagerInstance().getByUserName(memRef);
917                if (m == null) {
918                    m = InstanceManager.memoryManagerInstance().getBySystemName(memRef);
919                }
920                try {
921                    if (m == null || m.getValue() == null) {
922                        throw new NumberFormatException();
923                    }
924                    validateTime(actionType, Float.parseFloat((String) m.getValue()));
925                } catch (NumberFormatException ex) {
926                    JmriJOptionPane.showMessageDialog(_editLogixFrame,
927                            Bundle.getMessage("Error24", memRef),
928                            Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
929                }
930                return true;    // above is a warning to set memory correctly
931            }
932            displayBadNumberReference(actionType);
933        }
934        return false;
935    }
936
937    /**
938     * Range check time entry (assumes seconds).
939     *
940     * @param actionType integer representing the Conditional action type being
941     *                   checked, i.e. ACTION_DELAYED_TURNOUT
942     * @param time       value to be checked
943     * @return false if time &gt; 3600 (seconds) or too small
944     */
945    boolean validateTime(Conditional.Action actionType, float time) {
946        float maxTime = 3600;     // more than 1 hour
947        float minTime = 0.020f;
948        if (time < minTime || time > maxTime) {
949            String errorNum = " ";
950            switch (actionType) {
951                case DELAYED_TURNOUT:
952                    errorNum = "Error39";       // NOI18N
953                    break;
954                case RESET_DELAYED_TURNOUT:
955                    errorNum = "Error41";       // NOI18N
956                    break;
957                case DELAYED_SENSOR:
958                    errorNum = "Error23";       // NOI18N
959                    break;
960                case RESET_DELAYED_SENSOR:
961                    errorNum = "Error27";       // NOI18N
962                    break;
963                case SET_LIGHT_TRANSITION_TIME:
964                    errorNum = "Error29";       // NOI18N
965                    break;
966                default:
967                    break;
968            }
969            JmriJOptionPane.showMessageDialog(_editLogixFrame,
970                    Bundle.getMessage("Error38", time, Bundle.getMessage(errorNum)),
971                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
972            return false;
973        }
974        return true;
975    }
976
977    /**
978     * Display an error message to user when an invalid number is provided in
979     * Conditional setup.
980     *
981     * @param actionType integer representing the Conditional action type being
982     *                   checked, i.e. ACTION_DELAYED_TURNOUT
983     */
984    void displayBadNumberReference(@Nonnull Conditional.Action actionType) {
985        String errorNum = " ";
986        switch (actionType) {
987            case DELAYED_TURNOUT:
988                errorNum = "Error39";       // NOI18N
989                break;
990            case RESET_DELAYED_TURNOUT:
991                errorNum = "Error41";       // NOI18N
992                break;
993            case DELAYED_SENSOR:
994                errorNum = "Error23";       // NOI18N
995                break;
996            case RESET_DELAYED_SENSOR:
997                errorNum = "Error27";       // NOI18N
998                break;
999            case SET_LIGHT_INTENSITY:
1000                JmriJOptionPane.showMessageDialog(_editLogixFrame,
1001                        Bundle.getMessage("Error43"), // NOI18N
1002                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);       // NOI18N
1003                return;
1004            case SET_LIGHT_TRANSITION_TIME:
1005                errorNum = "Error29";       // NOI18N
1006                break;
1007            default:
1008                log.warn("Unexpected action type {} in displayBadNumberReference", actionType);  // NOI18N
1009        }
1010        JmriJOptionPane.showMessageDialog(_editLogixFrame,
1011                Bundle.getMessage("Error9", Bundle.getMessage(errorNum)),
1012                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);       // NOI18N
1013    }
1014
1015    /**
1016     * Check Memory reference of text.
1017     * <p>
1018     * Show a message if not found.
1019     *
1020     * @param name the name to look for
1021     * @return the system or user name of the corresponding Memory, null if not
1022     *         found
1023     */
1024    @CheckForNull
1025    String validateMemoryReference(@CheckForNull String name) {
1026        Memory m = null;
1027        if (name != null) {
1028            if (name.length() > 0) {
1029                m = InstanceManager.memoryManagerInstance().getByUserName(name);
1030                if (m != null) {
1031                    return name;
1032                }
1033            }
1034            m = InstanceManager.memoryManagerInstance().getBySystemName(name);
1035        }
1036        if (m == null) {
1037            messageInvalidActionItemName(name, "Memory"); // NOI18N
1038            return null;
1039        }
1040        return name;
1041    }
1042
1043    /**
1044     * Check if user will provide a valid item name in a Memory variable.
1045     *
1046     * @param memName Memory location to provide item name at run time
1047     * @return false if user replies No
1048     */
1049    boolean confirmIndirectMemory(String memName) {
1050        if (!_suppressIndirectRef) {
1051            int response = JmriJOptionPane.showConfirmDialog(_editLogixFrame,
1052                    Bundle.getMessage("ConfirmIndirectReference", memName,
1053                            Bundle.getMessage("ButtonYes"), Bundle.getMessage("ButtonNo"),
1054                            Bundle.getMessage("ButtonCancel")), // NOI18N
1055                    Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_CANCEL_OPTION, // NOI18N
1056                    JmriJOptionPane.QUESTION_MESSAGE);
1057            if (response == JmriJOptionPane.NO_OPTION || response == JmriJOptionPane.CLOSED_OPTION ) {
1058                return false;
1059            } else if (response == JmriJOptionPane.CANCEL_OPTION) {
1060                _suppressIndirectRef = true;
1061            }
1062        }
1063        return true;
1064    }
1065
1066    /**
1067     * Check if user OK's the use of an item as both an action and
1068     * a state variable.
1069     *
1070     * @param actionName name of ConditionalAction
1071     * @param variableName name of ConditionalVariable
1072     * @return false if user replies No
1073     */
1074    boolean confirmActionAsVariable(String actionName, String variableName) {
1075        int response = JmriJOptionPane.showConfirmDialog(_editLogixFrame,
1076                Bundle.getMessage("ConfirmActionAsVariable", actionName, variableName),
1077                Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION, // NOI18N
1078                JmriJOptionPane.QUESTION_MESSAGE);
1079        return ( response == JmriJOptionPane.YES_OPTION );
1080    }
1081
1082    /**
1083     * Check Turnout reference of text.
1084     * <p>
1085     * Show a message if not found.
1086     *
1087     * @param name the name to look for
1088     * @return the system or user name of the corresponding Turnout, null if not
1089     *         found
1090     */
1091    @CheckForNull
1092    String validateTurnoutReference(@CheckForNull String name) {
1093        Turnout t = null;
1094        if (name != null) {
1095            if (name.length() > 0) {
1096                t = InstanceManager.turnoutManagerInstance().getByUserName(name);
1097                if (t != null) {
1098                    return name;
1099                }
1100            }
1101            t = InstanceManager.turnoutManagerInstance().getBySystemName(name);
1102        }
1103        if (t == null) {
1104            messageInvalidActionItemName(name, "Turnout"); // NOI18N
1105            return null;
1106        }
1107        return name;
1108    }
1109
1110    /**
1111     * Check SignalHead reference of text.
1112     * <p>
1113     * Show a message if not found.
1114     *
1115     * @param name the name to look for
1116     * @return the system or user name of the corresponding SignalHead, null if
1117     *         not found
1118     */
1119    @CheckForNull
1120    String validateSignalHeadReference(@CheckForNull String name) {
1121        SignalHead h = null;
1122        if (name != null) {
1123            if (name.length() > 0) {
1124                h = InstanceManager.getDefault(jmri.SignalHeadManager.class).getByUserName(name);
1125                if (h != null) {
1126                    return name;
1127                }
1128            }
1129            h = InstanceManager.getDefault(jmri.SignalHeadManager.class).getBySystemName(name);
1130        }
1131        if (h == null) {
1132            messageInvalidActionItemName(name, "SignalHead"); // NOI18N
1133            return null;
1134        }
1135        return name;
1136    }
1137
1138    /**
1139     * Check SignalMast reference of text.
1140     * <p>
1141     * Show a message if not found.
1142     *
1143     * @param name the name to look for
1144     * @return the system or user name of the corresponding Signal Mast, null if
1145     *         not found
1146     */
1147    @CheckForNull
1148    String validateSignalMastReference(@CheckForNull String name) {
1149        SignalMast h = null;
1150        if (name != null) {
1151            if (name.length() > 0) {
1152                h = InstanceManager.getDefault(jmri.SignalMastManager.class).getByUserName(name);
1153                if (h != null) {
1154                    return name;
1155                }
1156            }
1157            try {
1158                h = InstanceManager.getDefault(jmri.SignalMastManager.class).provideSignalMast(name);
1159            } catch (IllegalArgumentException ex) {
1160                h = null; // tested below
1161            }
1162        }
1163        if (h == null) {
1164            messageInvalidActionItemName(name, "SignalMast"); // NOI18N
1165            return null;
1166        }
1167        return name;
1168    }
1169
1170    /**
1171     * Check Warrant reference of text.
1172     * <p>
1173     * Show a message if not found.
1174     *
1175     * @param name the name to look for
1176     * @return the system or user name of the corresponding Warrant, null if not
1177     *         found
1178     */
1179    @CheckForNull
1180    String validateWarrantReference(@CheckForNull String name) {
1181        Warrant w = null;
1182        if (name != null) {
1183            if (name.length() > 0) {
1184                w = InstanceManager.getDefault(WarrantManager.class).getByUserName(name);
1185                if (w != null) {
1186                    return name;
1187                }
1188            }
1189            w = InstanceManager.getDefault(WarrantManager.class).getBySystemName(name);
1190        }
1191        if (w == null) {
1192            messageInvalidActionItemName(name, "Warrant"); // NOI18N
1193            return null;
1194        }
1195        return name;
1196    }
1197
1198    /**
1199     * Check OBlock reference of text.
1200     * <p>
1201     * Show a message if not found.
1202     *
1203     * @param name the name to look for
1204     * @return the system or user name of the corresponding OBlock, null if not
1205     *         found
1206     */
1207    @CheckForNull
1208    String validateOBlockReference(@CheckForNull String name) {
1209        OBlock b = null;
1210        if (name != null) {
1211            if (name.length() > 0) {
1212                b = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getByUserName(name);
1213                if (b != null) {
1214                    return name;
1215                }
1216            }
1217            b = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getBySystemName(name);
1218        }
1219        if (b == null) {
1220            messageInvalidActionItemName(name, "OBlock"); // NOI18N
1221            return null;
1222        }
1223        return name;
1224    }
1225
1226    /**
1227     * Check Sensor reference of text.
1228     * <p>
1229     * Show a message if not found.
1230     *
1231     * @param name the name to look for
1232     * @return the system or user name of the corresponding Sensor, null if not
1233     *         found
1234     */
1235    @CheckForNull
1236    String validateSensorReference(@CheckForNull String name) {
1237        Sensor s = null;
1238        if (name != null) {
1239            if (name.length() > 0) {
1240                s = InstanceManager.getDefault(jmri.SensorManager.class).getByUserName(name);
1241                if (s != null) {
1242                    return name;
1243                }
1244            }
1245            s = InstanceManager.getDefault(jmri.SensorManager.class).getBySystemName(name);
1246        }
1247        if (s == null) {
1248            messageInvalidActionItemName(name, "Sensor"); // NOI18N
1249            return null;
1250        }
1251        return name;
1252    }
1253
1254    /**
1255     * Check Light reference of text.
1256     * <p>
1257     * Show a message if not found.
1258     *
1259     * @param name the name to look for
1260     * @return the system or user name of the corresponding Light, null if not
1261     *         found
1262     */
1263    @CheckForNull
1264    String validateLightReference(@CheckForNull String name) {
1265        Light l = null;
1266        if (name != null) {
1267            if (name.length() > 0) {
1268                l = InstanceManager.lightManagerInstance().getByUserName(name);
1269                if (l != null) {
1270                    return name;
1271                }
1272            }
1273            l = InstanceManager.lightManagerInstance().getBySystemName(name);
1274        }
1275        if (l == null) {
1276            messageInvalidActionItemName(name, "Light"); // NOI18N
1277            return null;
1278        }
1279        return name;
1280    }
1281
1282    /**
1283     * Check Conditional reference of text.
1284     * <p>
1285     * Show a message if not found.
1286     *
1287     * @param name the name to look for
1288     * @return the system or user name of the corresponding Conditional, null if
1289     *         not found
1290     */
1291    @CheckForNull
1292    String validateConditionalReference(@CheckForNull String name) {
1293        Conditional c = null;
1294        if (name != null) {
1295            if (name.length() > 0) {
1296                c = _conditionalManager.getByUserName(name);
1297                if (c != null) {
1298                    return name;
1299                }
1300            }
1301            c = _conditionalManager.getBySystemName(name);
1302        }
1303        if (c == null) {
1304            messageInvalidActionItemName(name, "Conditional"); // NOI18N
1305            return null;
1306        }
1307        return name;
1308    }
1309
1310    /**
1311     * Check Logix reference of text.
1312     * <p>
1313     * Show a message if not found.
1314     *
1315     * @param name the name to look for
1316     * @return the system or user name of the corresponding Logix, null if not
1317     *         found
1318     */
1319    @CheckForNull
1320    String validateLogixReference(@CheckForNull String name) {
1321        Logix l = null;
1322        if (name != null) {
1323            if (name.length() > 0) {
1324                l = _logixManager.getByUserName(name);
1325                if (l != null) {
1326                    return name;
1327                }
1328            }
1329            l = _logixManager.getBySystemName(name);
1330        }
1331        if (l == null) {
1332            messageInvalidActionItemName(name, "Logix"); // NOI18N
1333            return null;
1334        }
1335        return name;
1336    }
1337
1338    /**
1339     * Check Route reference of text.
1340     * <p>
1341     * Show a message if not found.
1342     *
1343     * @param name the name to look for
1344     * @return the system or user name of the corresponding Route, null if not
1345     *         found
1346     */
1347    @CheckForNull
1348    String validateRouteReference(@CheckForNull String name) {
1349        Route r = null;
1350        if (name != null) {
1351            if (name.length() > 0) {
1352                r = InstanceManager.getDefault(jmri.RouteManager.class).getByUserName(name);
1353                if (r != null) {
1354                    return name;
1355                }
1356            }
1357            r = InstanceManager.getDefault(jmri.RouteManager.class).getBySystemName(name);
1358        }
1359        if (r == null) {
1360            messageInvalidActionItemName(name, "Route"); // NOI18N
1361            return null;
1362        }
1363        return name;
1364    }
1365
1366    /**
1367     * Check an Audio reference of text.
1368     * <p>
1369     * Show a message if not found.
1370     *
1371     * @param name the name to look for
1372     * @return the system or user name of the corresponding AudioManager, null
1373     *         if not found
1374     */
1375    @CheckForNull
1376    String validateAudioReference(@CheckForNull String name) {
1377        Audio a = null;
1378        if (name != null) {
1379            if (name.length() > 0) {
1380                a = InstanceManager.getDefault(jmri.AudioManager.class).getByUserName(name);
1381                if (a != null) {
1382                    return name;
1383                }
1384            }
1385            a = InstanceManager.getDefault(jmri.AudioManager.class).getBySystemName(name);
1386        }
1387        if (a == null || (a.getSubType() != Audio.SOURCE && a.getSubType() != Audio.LISTENER)) {
1388            messageInvalidActionItemName(name, "Audio"); // NOI18N
1389            return null;
1390        }
1391        return name;
1392    }
1393
1394    /**
1395     * Check an EntryExit reference of text.
1396     * <p>
1397     * Show a message if not found.
1398     *
1399     * @param name the name to look for
1400     * @return the system name of the corresponding EntryExit pair, null if not
1401     *         found
1402     */
1403    @CheckForNull
1404    String validateEntryExitReference(@CheckForNull String name) {
1405        NamedBean nb;
1406        if (name != null && name.length() > 0) {
1407            nb = jmri.InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class).getNamedBean(name);
1408            if (nb != null) {
1409                return nb.getSystemName();
1410            }
1411        }
1412        messageInvalidActionItemName(name, "BeanNameEntryExit"); // NOI18N
1413        return null;
1414    }
1415
1416    /**
1417     * Get Light instance.
1418     * <p>
1419     * Show a message if not found.
1420     *
1421     * @param name user or system name of an existing light
1422     * @return the Light object
1423     */
1424    @CheckForNull
1425    Light getLight( @CheckForNull String name) {
1426        if (name == null) {
1427            return null;
1428        }
1429        Light l = null;
1430        if (name.length() > 0) {
1431            l = InstanceManager.lightManagerInstance().getByUserName(name);
1432            if (l != null) {
1433                return l;
1434            }
1435            l = InstanceManager.lightManagerInstance().getBySystemName(name);
1436        }
1437        if (l == null) {
1438            messageInvalidActionItemName(name, "Light"); // NOI18N
1439        }
1440        return l;
1441    }
1442
1443    int parseTime(@Nonnull String s) {
1444        int nHour = 0;
1445        int nMin = 0;
1446        boolean error = false;
1447        int index = s.indexOf(':');
1448        String hour = null;
1449        String minute = null;
1450        try {
1451            if (index > 0) { // : after start
1452                hour = s.substring(0, index);
1453                if (index + 1 < s.length()) { // check for : at end
1454                    minute = s.substring(index + 1);
1455                } else {
1456                    minute = "0";
1457                }
1458            } else if (index == 0) { // : at start
1459                hour = "0";
1460                minute = s.substring(index + 1);
1461            } else {
1462                hour = s;
1463                minute = "0";
1464            }
1465        } catch (IndexOutOfBoundsException ioob) {
1466            error = true;
1467        }
1468        if (!error) {
1469            try {
1470                nHour = Integer.parseInt(hour);
1471                if ((nHour < 0) || (nHour > 24)) {
1472                    error = true;
1473                }
1474                nMin = Integer.parseInt(minute);
1475                if ((nMin < 0) || (nMin > 59)) {
1476                    error = true;
1477                }
1478            } catch (NumberFormatException e) {
1479                error = true;
1480            }
1481        }
1482        if (error) {
1483            // if unsuccessful, print error message
1484            JmriJOptionPane.showMessageDialog(_editLogixFrame,
1485                    Bundle.getMessage("Error26", s),
1486                    Bundle.getMessage("ErrorTitle"), // NOI18N
1487                    JmriJOptionPane.ERROR_MESSAGE);
1488            return (-1);
1489        }
1490        // here if successful
1491        return ((nHour * 60) + nMin);
1492    }
1493
1494    /**
1495     * Format time to hh:mm given integer hour and minute.
1496     *
1497     * @param hour   value for time hours
1498     * @param minute value for time minutes
1499     * @return Formatted time string
1500     */
1501    public static String formatTime(int hour, int minute) {
1502        String s = "";
1503        String t = Integer.toString(hour);
1504        if (t.length() == 2) {
1505            s = t + ":";
1506        } else if (t.length() == 1) {
1507            s = "0" + t + ":";
1508        }
1509        t = Integer.toString(minute);
1510        if (t.length() == 2) {
1511            s += t;
1512        } else if (t.length() == 1) {
1513            s = s + "0" + t;
1514        }
1515        if (s.length() != 5) {
1516            // input error
1517            s = "00:00";
1518        }
1519        return s;
1520    }
1521
1522    // ------------ Error Dialogs ------------
1523
1524    /**
1525     * Send an Invalid Conditional Action name message for Edit Logix pane.
1526     *
1527     * @param name     user or system name to look up
1528     * @param itemType type of Bean to look for
1529     */
1530    void messageInvalidActionItemName(String name, String itemType) {
1531        JmriJOptionPane.showMessageDialog(_editLogixFrame,
1532                Bundle.getMessage("Error22", name, Bundle.getMessage("BeanName" + itemType)),
1533                Bundle.getMessage("ErrorTitle"), // NOI18N
1534                JmriJOptionPane.ERROR_MESSAGE);
1535    }
1536
1537    /**
1538     * Send a duplicate Conditional user name message for Edit Logix pane.
1539     *
1540     * @param svName proposed name that duplicates an existing name
1541     */
1542    void messageDuplicateConditionalUserName(String svName) {
1543        JmriJOptionPane.showMessageDialog(_editLogixFrame,
1544                Bundle.getMessage("Error30", svName),
1545                Bundle.getMessage("ErrorTitle"), // NOI18N
1546                JmriJOptionPane.ERROR_MESSAGE);
1547    }
1548
1549    public void bringToFront() {
1550        _editLogixFrame.toFront();
1551    }
1552
1553    public void locateAt(Component c) {
1554        _editLogixFrame.setLocationRelativeTo(c);
1555        _editLogixFrame.toFront();
1556    }
1557
1558    protected String getClassName() {
1559        return ConditionalEditBase.class.getName();
1560    }
1561
1562    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConditionalEditBase.class);
1563
1564}