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