001package jmri.jmrit.beantable;
002
003import java.awt.BorderLayout;
004import java.awt.Component;
005import java.awt.Container;
006import java.awt.FlowLayout;
007import java.awt.Font;
008import java.awt.event.ActionEvent;
009import java.awt.event.ActionListener;
010import java.awt.event.ItemEvent;
011import java.awt.event.ItemListener;
012import java.awt.event.KeyEvent;
013import java.io.File;
014import java.io.IOException;
015import java.util.ArrayList;
016import java.util.HashMap;
017import java.util.List;
018import java.util.SortedSet;
019import java.util.TreeSet;
020import javax.annotation.Nonnull;
021import javax.swing.BorderFactory;
022import javax.swing.Box;
023import javax.swing.BoxLayout;
024import javax.swing.ButtonGroup;
025import javax.swing.DefaultCellEditor;
026import javax.swing.JButton;
027import javax.swing.JCheckBox;
028import javax.swing.JComboBox;
029import javax.swing.JComponent;
030import javax.swing.JDialog;
031import javax.swing.JFileChooser;
032import javax.swing.JLabel;
033import javax.swing.JMenu;
034import javax.swing.JMenuItem;
035import javax.swing.JOptionPane;
036import javax.swing.JPanel;
037import javax.swing.JRadioButtonMenuItem;
038import javax.swing.JScrollPane;
039import javax.swing.JTable;
040import javax.swing.JTextArea;
041import javax.swing.JTextField;
042import javax.swing.table.TableColumn;
043import jmri.Conditional;
044import jmri.ConditionalAction;
045import jmri.ConditionalManager;
046import jmri.ConditionalVariable;
047import jmri.InstanceManager;
048import jmri.Logix;
049import jmri.LogixManager;
050import jmri.Manager;
051import jmri.UserPreferencesManager;
052import jmri.NamedBean.DisplayOptions;
053import jmri.jmrit.conditional.ConditionalEditBase;
054import jmri.jmrit.conditional.ConditionalListEdit;
055import jmri.jmrit.conditional.ConditionalTreeEdit;
056import jmri.jmrit.conditional.ConditionalListCopy;
057import jmri.jmrit.sensorgroup.SensorGroupFrame;
058import jmri.util.FileUtil;
059import jmri.util.JmriJFrame;
060import org.slf4j.Logger;
061import org.slf4j.LoggerFactory;
062
063/**
064 * Swing action to create and register a Logix Table.
065 * <p>
066 * Also contains the panes to create, edit, and delete a Logix. Conditional
067 * editing has been moved to ConditionalListView or CondtionalTreeView.
068 * <p>
069 * Most of the text used in this GUI is in BeanTableBundle.properties, accessed
070 * via Bundle.getMessage().
071 * 201803 Moved all keys from LogixTableBundle.properties to
072 * BeanTableBundle.properties to simplify i18n.
073 * <p>
074 * Conditionals now have two policies to trigger execution of their action lists:
075 * <ol>
076 *     <li>the previous policy - Trigger on change of state only
077 *     <li>the new default - Trigger on any enabled state calculation
078 * </ol>
079 * Jan 15, 2011 - Pete Cressman
080 * <p>
081 * Two additional action and variable name selection methods have been added:
082 * <ol>
083 *     <li>Single Pick List
084 *     <li>Combo Box Selection
085 * </ol>
086 * The traditional tabbed Pick List with text entry is the default method.
087 * The Options menu has been expanded to list the 3 methods.
088 * Mar 27, 2017 - Dave Sand
089 * <p>
090 * Add a Browse Option to the Logix Select Menu This will display a window that
091 * creates a formatted list of the contents of the selected Logix with each
092 * Conditional, Variable and Action. The code is courtesy of Chuck Catania and
093 * is used with his permission. Apr 2, 2017 - Dave Sand
094 *
095 * @author Dave Duchamp Copyright (C) 2007
096 * @author Pete Cressman Copyright (C) 2009, 2010, 2020
097 * @author Matthew Harris copyright (c) 2009
098 * @author Dave Sand copyright (c) 2017
099 */
100public class LogixTableAction extends AbstractTableAction<Logix> {
101
102    /**
103     * Create a LogixManager instance.
104     *
105     * @param s the Action title, not the title of the resulting frame. Perhaps
106     *          this should be changed?
107     */
108    public LogixTableAction(String s) {
109        super(s);
110        // set up managers - no need to use InstanceManager since both managers are
111        // Default only (internal). We use InstanceManager to get managers for
112        // compatibility with other facilities.
113        _logixManager = InstanceManager.getNullableDefault(jmri.LogixManager.class);
114        _conditionalManager = InstanceManager.getNullableDefault(jmri.ConditionalManager.class);
115        // disable ourself if there is no Logix manager or no Conditional manager available
116        if ((_logixManager == null) || (_conditionalManager == null)) {
117            setEnabled(false);
118        }
119    }
120
121    /**
122     * Create a LogixManager instance with default title.
123     */
124    public LogixTableAction() {
125        this(Bundle.getMessage("TitleLogixTable"));
126    }
127
128    // ------------ Methods for Logix Table Window ------------
129
130    /**
131     * Create the JTable DataModel, along with the changes (overrides of
132     * BeanTableDataModel) for the specific case of a Logix table.
133     */
134    @Override
135    protected void createModel() {
136        m = new BeanTableDataModel<Logix>() {
137            // overlay the state column with the edit column
138            static public final int ENABLECOL = VALUECOL;
139            static public final int EDITCOL = DELETECOL;
140            protected String enabledString = Bundle.getMessage("ColumnHeadEnabled");  // NOI18N
141
142            @Override
143            public String getColumnName(int col) {
144                if (col == EDITCOL) {
145                    return "";  // no heading on "Edit"
146                }
147                if (col == ENABLECOL) {
148                    return enabledString;
149                }
150                return super.getColumnName(col);
151            }
152
153            @Override
154            public Class<?> getColumnClass(int col) {
155                if (col == EDITCOL) {
156                    return String.class;
157                }
158                if (col == ENABLECOL) {
159                    return Boolean.class;
160                }
161                return super.getColumnClass(col);
162            }
163
164            @Override
165            public int getPreferredWidth(int col) {
166                // override default value for SystemName and UserName columns
167                if (col == SYSNAMECOL) {
168                    return new JTextField(12).getPreferredSize().width;
169                }
170                if (col == USERNAMECOL) {
171                    return new JTextField(17).getPreferredSize().width;
172                }
173                if (col == EDITCOL) {
174                    return new JTextField(12).getPreferredSize().width;
175                }
176                if (col == ENABLECOL) {
177                    return new JTextField(5).getPreferredSize().width;
178                }
179                return super.getPreferredWidth(col);
180            }
181
182            @Override
183            public boolean isCellEditable(int row, int col) {
184                if (col == EDITCOL) {
185                    return true;
186                }
187                if (col == ENABLECOL) {
188                    return true;
189                }
190                return super.isCellEditable(row, col);
191            }
192
193            @Override
194            public Object getValueAt(int row, int col) {
195                if (col == EDITCOL) {
196                    return Bundle.getMessage("ButtonSelect");  // NOI18N
197                } else if (col == ENABLECOL) {
198                    Logix logix = (Logix) getValueAt(row, SYSNAMECOL);
199                    if (logix == null) {
200                        return null;
201                    }
202                    return Boolean.valueOf(logix.getEnabled());
203                } else {
204                    return super.getValueAt(row, col);
205                }
206            }
207
208            @Override
209            public void setValueAt(Object value, int row, int col) {
210                if (col == EDITCOL) {
211                    // set up to edit
212                    String sName = ((Logix) getValueAt(row, SYSNAMECOL)).getSystemName();
213                    if (Bundle.getMessage("ButtonEdit").equals(value)) {  // NOI18N
214                        editPressed(sName);
215
216                    } else if (Bundle.getMessage("BrowserButton").equals(value)) {  // NOI18N
217                        conditionalRowNumber = row;
218                        browserPressed(sName);
219
220                    } else if (Bundle.getMessage("ButtonCopy").equals(value)) {  // NOI18N
221                        copyPressed(sName);
222
223                    } else if (Bundle.getMessage("ButtonDelete").equals(value)) {  // NOI18N
224                        deletePressed(sName);
225                    }
226                } else if (col == ENABLECOL) {
227                    // alternate
228                    Logix x = (Logix) getValueAt(row, SYSNAMECOL);
229                    boolean v = x.getEnabled();
230                    x.setEnabled(!v);
231                } else {
232                    super.setValueAt(value, row, col);
233                }
234            }
235
236            /**
237             * Delete the bean after all the checking has been done.
238             * <p>
239             * Deactivates the Logix and remove its conditionals.
240             *
241             * @param bean of the Logix to delete
242             */
243            @Override
244            protected void doDelete(Logix bean) {
245                bean.deActivateLogix();
246                // delete the Logix and all its Conditionals
247                _logixManager.deleteLogix(bean);
248            }
249
250            @Override
251            protected boolean matchPropertyName(java.beans.PropertyChangeEvent e) {
252                if (e.getPropertyName().equals(enabledString)) {
253                    return true;
254                }
255                return super.matchPropertyName(e);
256            }
257
258            @Override
259            public Manager<Logix> getManager() {
260                return InstanceManager.getDefault(jmri.LogixManager.class);
261            }
262
263            @Override
264            public Logix getBySystemName(@Nonnull String name) {
265                return InstanceManager.getDefault(jmri.LogixManager.class).getBySystemName(
266                        name);
267            }
268
269            @Override
270            public Logix getByUserName(@Nonnull String name) {
271                return InstanceManager.getDefault(jmri.LogixManager.class).getByUserName(
272                        name);
273            }
274
275            @Override
276            protected String getMasterClassName() {
277                return getClassName();
278            }
279
280            @Override
281            public void configureTable(JTable table) {
282                table.setDefaultRenderer(Boolean.class, new EnablingCheckboxRenderer());
283                table.setDefaultRenderer(JComboBox.class, new jmri.jmrit.symbolicprog.ValueRenderer());
284                table.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor());
285                super.configureTable(table);
286            }
287
288            /**
289             * Replace delete button with comboBox to edit/delete/copy/select
290             * Logix.
291             *
292             * @param table name of the Logix JTable holding the column
293             */
294            @Override
295            protected void configDeleteColumn(JTable table) {
296                JComboBox<String> editCombo = new JComboBox<String>();
297                editCombo.addItem(Bundle.getMessage("ButtonSelect"));  // NOI18N
298                editCombo.addItem(Bundle.getMessage("ButtonEdit"));  // NOI18N
299                editCombo.addItem(Bundle.getMessage("BrowserButton"));  // NOI18N
300                editCombo.addItem(Bundle.getMessage("ButtonCopy"));  // NOI18N
301                editCombo.addItem(Bundle.getMessage("ButtonDelete"));  // NOI18N
302                TableColumn col = table.getColumnModel().getColumn(BeanTableDataModel.DELETECOL);
303                col.setCellEditor(new DefaultCellEditor(editCombo));
304            }
305
306            // Not needed - here for interface compatibility
307            @Override
308            public void clickOn(Logix t) {
309            }
310
311            @Override
312            public String getValue(String s) {
313                return "";
314            }
315
316            @Override
317            protected String getBeanType() {
318                return Bundle.getMessage("BeanNameLogix");  // NOI18N
319            }
320        };
321    }
322
323    /**
324     * Set title of Logix table.
325     */
326    @Override
327    protected void setTitle() {
328        f.setTitle(Bundle.getMessage("TitleLogixTable"));
329    }
330
331    /**
332     * Insert 2 table specific menus.
333     * <p>
334     * Accounts for the Window and Help menus, which are already added to the
335     * menu bar as part of the creation of the JFrame, by adding the new menus 2
336     * places earlier unless the table is part of the ListedTableFrame, which
337     * adds the Help menu later on.
338     *
339     * @param f the JFrame of this table
340     */
341    @Override
342    public void setMenuBar(BeanTableFrame f) {
343        loadSelectionMode();
344        loadEditorMode();
345
346        JMenu menu = new JMenu(Bundle.getMessage("MenuOptions"));  // NOI18N
347        menu.setMnemonic(KeyEvent.VK_O);
348        javax.swing.JMenuBar menuBar = f.getJMenuBar();
349        int pos = menuBar.getMenuCount() - 1;  // count the number of menus to insert the TableMenus before 'Window' and 'Help'
350        int offset = 1;
351        log.debug("setMenuBar number of menu items = {}", pos);  // NOI18N
352        for (int i = 0; i <= pos; i++) {
353            if (menuBar.getComponent(i) instanceof JMenu) {
354                if (((JMenu) menuBar.getComponent(i)).getText().equals(Bundle.getMessage("MenuHelp"))) {  // NOI18N
355                    offset = -1;  // correct for use as part of ListedTableAction where the Help Menu is not yet present
356                }
357            }
358        }
359
360        ButtonGroup enableButtonGroup = new ButtonGroup();
361        JRadioButtonMenuItem r = new JRadioButtonMenuItem(Bundle.getMessage("EnableAll"));  // NOI18N
362        r.addActionListener(new ActionListener() {
363            @Override
364            public void actionPerformed(ActionEvent e) {
365                enableAll(true);
366            }
367        });
368        enableButtonGroup.add(r);
369        r.setSelected(true);
370        menu.add(r);
371
372        r = new JRadioButtonMenuItem(Bundle.getMessage("DisableAll"));  // NOI18N
373        r.addActionListener(new ActionListener() {
374            @Override
375            public void actionPerformed(ActionEvent e) {
376                enableAll(false);
377            }
378        });
379        enableButtonGroup.add(r);
380        menu.add(r);
381
382        menu.addSeparator();
383
384        ButtonGroup modeButtonGroup = new ButtonGroup();
385        r = new JRadioButtonMenuItem(Bundle.getMessage("UseMultiPick"));  // NOI18N
386        r.addItemListener(new ItemListener() {
387            @Override
388            public void itemStateChanged(ItemEvent e) {
389                setSelectionMode(SelectionMode.USEMULTI);
390            }
391        });
392        modeButtonGroup.add(r);
393        menu.add(r);
394        r.setSelected(_selectionMode == SelectionMode.USEMULTI);
395
396        r = new JRadioButtonMenuItem(Bundle.getMessage("UseSinglePick"));  // NOI18N
397        r.addItemListener(new ItemListener() {
398            @Override
399            public void itemStateChanged(ItemEvent e) {
400                setSelectionMode(SelectionMode.USESINGLE);
401            }
402        });
403        modeButtonGroup.add(r);
404        menu.add(r);
405        r.setSelected(_selectionMode == SelectionMode.USESINGLE);
406
407        r = new JRadioButtonMenuItem(Bundle.getMessage("UseComboNameBoxes"));  // NOI18N
408        r.addItemListener(new ItemListener() {
409            @Override
410            public void itemStateChanged(ItemEvent e) {
411                setSelectionMode(SelectionMode.USECOMBO);
412            }
413        });
414        modeButtonGroup.add(r);
415        menu.add(r);
416        r.setSelected(_selectionMode == SelectionMode.USECOMBO);
417
418        menu.addSeparator();
419
420        ButtonGroup viewButtonGroup = new ButtonGroup();
421        r = new JRadioButtonMenuItem(Bundle.getMessage("ListEdit"));  // NOI18N
422        r.addItemListener(new ItemListener() {
423            @Override
424            public void itemStateChanged(ItemEvent e) {
425                setEditorMode(EditMode.LISTEDIT);
426            }
427        });
428        viewButtonGroup.add(r);
429        menu.add(r);
430        r.setSelected(_editMode == EditMode.LISTEDIT);
431
432        r = new JRadioButtonMenuItem(Bundle.getMessage("TreeEdit"));  // NOI18N
433        r.addItemListener(new ItemListener() {
434            @Override
435            public void itemStateChanged(ItemEvent e) {
436                setEditorMode(EditMode.TREEEDIT);
437            }
438        });
439        viewButtonGroup.add(r);
440        menu.add(r);
441        r.setSelected(_editMode == EditMode.TREEEDIT);
442
443        menuBar.add(menu, pos + offset);
444
445        menu = new JMenu(Bundle.getMessage("MenuTools"));  // NOI18N
446        menu.setMnemonic(KeyEvent.VK_T);
447
448        JMenuItem item = new JMenuItem(Bundle.getMessage("OpenPickListTables"));  // NOI18N
449        item.addActionListener(new ActionListener() {
450            @Override
451            public void actionPerformed(ActionEvent e) {
452                openPickListTable();
453            }
454        });
455        menu.add(item);
456
457        item = new JMenuItem(Bundle.getMessage("FindOrphans"));  // NOI18N
458        item.addActionListener(new ActionListener() {
459            @Override
460            public void actionPerformed(ActionEvent e) {
461                findOrphansPressed(e);
462            }
463        });
464        menu.add(item);
465
466        item = new JMenuItem(Bundle.getMessage("EmptyConditionals"));  // NOI18N
467        item.addActionListener(new ActionListener() {
468            @Override
469            public void actionPerformed(ActionEvent e) {
470                findEmptyPressed(e);
471            }
472        });
473        menu.add(item);
474
475        item = new JMenuItem(Bundle.getMessage("CrossReference"));  // NOI18N
476        item.addActionListener(new ActionListener() {
477            BeanTableFrame parent;
478
479            @Override
480            public void actionPerformed(ActionEvent e) {
481                new RefDialog(parent);
482            }
483
484            ActionListener init(BeanTableFrame f) {
485                parent = f;
486                return this;
487            }
488        }.init(f));
489        menu.add(item);
490
491        item = new JMenuItem(Bundle.getMessage("DisplayWhereUsed"));  // NOI18N
492        item.addActionListener(new ActionListener() {
493            @Override
494            public void actionPerformed(ActionEvent e) {
495                makeWhereUsedWindow();
496            }
497        });
498        menu.add(item);
499
500        menuBar.add(menu, pos + offset + 1);  // add this menu to the right of the previous
501    }
502
503    /**
504     * Get the saved mode selection, default to the tranditional tabbed pick
505     * list.
506     * <p>
507     * During the menu build process, the corresponding menu item is set to
508     * selected.
509     *
510     * @since 4.7.3
511     */
512    void loadSelectionMode() {
513        Object modeName = InstanceManager.getDefault(jmri.UserPreferencesManager.class).
514                getProperty(getClassName(), "Selection Mode");      // NOI18N
515        if (modeName == null) {
516            _selectionMode = SelectionMode.USEMULTI;
517        } else {
518            String currentMode = (String) modeName;
519            switch (currentMode) {
520                case "USEMULTI":        // NOI18N
521                    _selectionMode = SelectionMode.USEMULTI;
522                    break;
523                case "USESINGLE":       // NOI18N
524                    _selectionMode = SelectionMode.USESINGLE;
525                    break;
526                case "USECOMBO":        // NOI18N
527                    _selectionMode = SelectionMode.USECOMBO;
528                    break;
529                default:
530                    log.warn("Invalid Logix conditional selection mode value, '{}', returned", currentMode);  // NOI18N
531                    _selectionMode = SelectionMode.USEMULTI;
532            }
533        }
534    }
535
536    /**
537     * Save the mode selection. Called by menu item change events.
538     *
539     * @since 4.7.3
540     * @param newMode The SelectionMode enum constant
541     */
542    void setSelectionMode(SelectionMode newMode) {
543        _selectionMode = newMode;
544        InstanceManager.getDefault(jmri.UserPreferencesManager.class).
545                setProperty(getClassName(), "Selection Mode", newMode.toString());  // NOI18N
546    }
547
548    /**
549     * Get the saved mode selection, default to the tranditional conditional
550     * list editor.
551     * <p>
552     * During the menu build process, the corresponding menu item is set to
553     * selected.
554     *
555     * @since 4.9.x
556     */
557    void loadEditorMode() {
558        Object modeName = InstanceManager.getDefault(jmri.UserPreferencesManager.class).
559                getProperty(getClassName(), "Edit Mode");      // NOI18N
560        if (modeName == null) {
561            _editMode = EditMode.LISTEDIT;
562        } else {
563            String currentMode = (String) modeName;
564            switch (currentMode) {
565                case "LISTEDIT":        // NOI18N
566                    _editMode = EditMode.LISTEDIT;
567                    break;
568                case "TREEEDIT":       // NOI18N
569                    _editMode = EditMode.TREEEDIT;
570                    break;
571                default:
572                    log.warn("Invalid conditional edit mode value, '{}', returned", currentMode);  // NOI18N
573                    _editMode = EditMode.LISTEDIT;
574            }
575        }
576    }
577
578    /**
579     * Save the view mode selection. Called by menu item change events.
580     *
581     * @since 4.9.x
582     * @param newMode The ViewMode enum constant
583     */
584    void setEditorMode(EditMode newMode) {
585        _editMode = newMode;
586        InstanceManager.getDefault(jmri.UserPreferencesManager.class).
587                setProperty(getClassName(), "Edit Mode", newMode.toString());  // NOI18N
588    }
589
590    /**
591     * Open a new Pick List to drag Actions from to form Logix Conditionals.
592     */
593    void openPickListTable() {
594        if (_pickTables == null) {
595            _pickTables = new jmri.jmrit.picker.PickFrame(Bundle.getMessage("TitlePickList"));  // NOI18N
596        } else {
597            _pickTables.setVisible(true);
598        }
599        _pickTables.toFront();
600    }
601
602    /**
603     * Find empty Conditional entries, called from menu.
604     *
605     * @see Maintenance#findEmptyPressed(java.awt.Frame)
606     * @param e the event heard
607     */
608    void findEmptyPressed(ActionEvent e) {
609        Maintenance.findEmptyPressed(f);
610    }
611
612    /**
613     * Find orphaned entries, called from menu.
614     *
615     * @see Maintenance#findOrphansPressed(java.awt.Frame)
616     * @param e the event heard
617     */
618    void findOrphansPressed(ActionEvent e) {
619        Maintenance.findOrphansPressed(f);
620    }
621
622    class RefDialog extends JDialog {
623
624        JTextField _devNameField;
625        java.awt.Frame _parent;
626
627        RefDialog(java.awt.Frame frame) {
628            super(frame, Bundle.getMessage("CrossReference"), true);  // NOI18N
629            _parent = frame;
630            JPanel extraPanel = new JPanel();
631            extraPanel.setLayout(new BoxLayout(extraPanel, BoxLayout.Y_AXIS));
632            _devNameField = new JTextField(30);
633            JPanel panel = makeEditPanel(_devNameField, "ElementName", "ElementNameHint");  // NOI18N
634            JButton referenceButton = new JButton(Bundle.getMessage("ReferenceButton"));  // NOI18N
635            panel.add(referenceButton);
636            referenceButton.addActionListener(new ActionListener() {
637                @Override
638                public void actionPerformed(ActionEvent e) {
639                    deviceReportPressed(e);
640                }
641            });
642            panel.add(referenceButton);
643            extraPanel.add(panel);
644            setContentPane(extraPanel);
645            pack();
646            // setLocationRelativeTo((java.awt.Component)_pos);
647            setVisible(true);
648        }
649
650        void deviceReportPressed(ActionEvent e) {
651            Maintenance.deviceReportPressed(_devNameField.getText(), _parent);
652            dispose();
653        }
654    }
655
656    void enableAll(boolean enable) {
657        for (Logix x : _logixManager.getNamedBeanSet()) {
658            x.setEnabled(enable);
659        }
660    }
661
662    @Override
663    protected String helpTarget() {
664        return "package.jmri.jmrit.beantable.LogixTable";  // NOI18N
665    }
666
667    // ------------ variable definitions ------------
668
669    // Multi use variables
670    ConditionalManager _conditionalManager = null;  // set when LogixAction is created
671    LogixManager _logixManager = null;  // set when LogixAction is created
672
673    ConditionalEditBase _baseEdit;
674
675    boolean _showReminder = false;
676    jmri.jmrit.picker.PickFrame _pickTables;
677
678    // Current focus variables
679    Logix _curLogix = null;
680    int conditionalRowNumber = 0;
681
682    // Add Logix Variables
683    JmriJFrame addLogixFrame = null;
684    JTextField _systemName = new JTextField(20);
685    JTextField _addUserName = new JTextField(20);
686    JCheckBox _autoSystemName = new JCheckBox(Bundle.getMessage("LabelAutoSysName"));   // NOI18N
687    JLabel _sysNameLabel = new JLabel(Bundle.getMessage("BeanNameLogix") + " " + Bundle.getMessage("ColumnSystemName") + ":");  // NOI18N
688    JLabel _userNameLabel = new JLabel(Bundle.getMessage("BeanNameLogix") + " " + Bundle.getMessage("ColumnUserName") + ":");   // NOI18N
689    String systemNameAuto = this.getClass().getName() + ".AutoSystemName";      // NOI18N
690    JButton create;
691
692    // Edit Logix Variables
693    private boolean _inEditMode = false;
694    private boolean _inCopyMode = false;
695    private boolean _inAddMode = false;
696
697    /**
698     * Input selection names.
699     *
700     * @since 4.7.3
701     */
702    public enum SelectionMode {
703        /**
704         * Use the traditional text field, with the tabbed Pick List available
705         * for drag-n-drop
706         */
707        USEMULTI,
708        /**
709         * Use the traditional text field, but with a single Pick List that
710         * responds with a click
711         */
712        USESINGLE,
713        /**
714         * Use combo boxes to select names instead of a text field.
715         */
716        USECOMBO;
717    }
718    SelectionMode _selectionMode;
719
720    /**
721     * Conditional edit view mode
722     *
723     * @since 4.9.x
724     */
725    public enum EditMode {
726        /**
727         * Use the traditional table list mode for editing conditionals
728         */
729        LISTEDIT,
730        /**
731         * Use the tree based mode for editing condtiionals
732         */
733        TREEEDIT;
734    }
735    EditMode _editMode;
736
737    // Save conditional reference target names before updating
738    private TreeSet<String> _saveTargetNames = new TreeSet<String>();
739    private HashMap<String, ArrayList<String>> _saveTargetList = new HashMap<>();
740
741    // ------------ Methods for Add Logix Window ------------
742
743    /**
744     * Respond to the Add button in Logix table Creates and/or initialize the
745     * Add Logix pane.
746     *
747     * @param e The event heard
748     */
749    @Override
750    protected void addPressed(ActionEvent e) {
751        // possible change
752        if (!checkFlags(null)) {
753            return;
754        }
755        _showReminder = true;
756        // make an Add Logix Frame
757        if (addLogixFrame == null) {
758            JPanel panel5 = makeAddLogixFrame("TitleAddLogix", "AddLogixMessage", 
759                    "package.jmri.jmrit.beantable.LogixAddEdit");  // NOI18N
760            // Create Logix
761            create = new JButton(Bundle.getMessage("ButtonCreate"));  // NOI18N
762            panel5.add(create);
763            create.addActionListener(new ActionListener() {
764                @Override
765                public void actionPerformed(ActionEvent e) {
766                    createPressed(e);
767                }
768            });
769            create.setToolTipText(Bundle.getMessage("LogixCreateButtonHint"));  // NOI18N
770        }
771        _inAddMode = true;
772        addLogixFrame.setEscapeKeyClosesWindow(true);
773        addLogixFrame.getRootPane().setDefaultButton(create);
774        addLogixFrame.pack();
775        addLogixFrame.setVisible(true);
776        _autoSystemName.setSelected(false);
777        addLogixFrame.setLocationRelativeTo(getFrame());
778        InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
779            _autoSystemName.setSelected(prefMgr.getSimplePreferenceState(systemNameAuto));
780        });
781    }
782
783    /**
784     * Create or copy Logix frame.
785     *
786     * @param titleId   property key to fetch as title of the frame (using Bundle)
787     * @param messageId part 1 of property key to fetch as user instruction on
788     *                  pane, either 1 or 2 is added to form the whole key
789     * @param helpFile help file name
790     * @return the button JPanel
791     */
792    JPanel makeAddLogixFrame(String titleId, String messageId, String helpFile) {
793        addLogixFrame = new JmriJFrame(Bundle.getMessage(titleId));
794        addLogixFrame.addHelpMenu(helpFile, true);     // NOI18N
795        addLogixFrame.setLocation(50, 30);
796        Container contentPane = addLogixFrame.getContentPane();
797        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
798
799        JPanel p;
800        p = new JPanel();
801        p.setLayout(new FlowLayout());
802        p.setLayout(new java.awt.GridBagLayout());
803        java.awt.GridBagConstraints c = new java.awt.GridBagConstraints();
804        c.gridwidth = 1;
805        c.gridheight = 1;
806        c.gridx = 0;
807        c.gridy = 0;
808        c.anchor = java.awt.GridBagConstraints.EAST;
809        p.add(_sysNameLabel, c);
810        c.gridy = 1;
811        p.add(_userNameLabel, c);
812        c.gridx = 1;
813        c.gridy = 0;
814        c.anchor = java.awt.GridBagConstraints.WEST;
815        c.weightx = 1.0;
816        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
817        p.add(_systemName, c);
818        c.gridy = 1;
819        p.add(_addUserName, c);
820        c.gridx = 2;
821        c.gridy = 1;
822        c.anchor = java.awt.GridBagConstraints.WEST;
823        c.weightx = 1.0;
824        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
825        c.gridy = 0;
826        p.add(_autoSystemName, c);
827        _addUserName.setToolTipText(Bundle.getMessage("LogixUserNameHint"));    // NOI18N
828        _systemName.setToolTipText(Bundle.getMessage("LogixSystemNameHint"));   // NOI18N
829        contentPane.add(p);
830        // set up message
831        JPanel panel3 = new JPanel();
832        panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS));
833        JPanel panel31 = new JPanel();
834        panel31.setLayout(new FlowLayout());
835        JLabel message1 = new JLabel(Bundle.getMessage(messageId + "1"));  // NOI18N
836        panel31.add(message1);
837        JPanel panel32 = new JPanel();
838        JLabel message2 = new JLabel(Bundle.getMessage(messageId + "2"));  // NOI18N
839        panel32.add(message2);
840        panel3.add(panel31);
841        panel3.add(panel32);
842        contentPane.add(panel3);
843
844        // set up create and cancel buttons
845        JPanel panel5 = new JPanel();
846        panel5.setLayout(new FlowLayout());
847        // Cancel
848        JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));    // NOI18N
849        panel5.add(cancel);
850        cancel.addActionListener(new ActionListener() {
851            @Override
852            public void actionPerformed(ActionEvent e) {
853                cancelAddPressed(e);
854            }
855        });
856        cancel.setToolTipText(Bundle.getMessage("CancelLogixButtonHint"));      // NOI18N
857
858        addLogixFrame.addWindowListener(new java.awt.event.WindowAdapter() {
859            @Override
860            public void windowClosing(java.awt.event.WindowEvent e) {
861                cancelAddPressed(null);
862            }
863        });
864        contentPane.add(panel5);
865
866        _autoSystemName.addItemListener(
867                new ItemListener() {
868            @Override
869            public void itemStateChanged(ItemEvent e) {
870                autoSystemName();
871            }
872        });
873        return panel5;
874    }
875
876    /**
877     * Enable/disable fields for data entry when user selects to have system
878     * name automatically generated.
879     */
880    void autoSystemName() {
881        if (_autoSystemName.isSelected()) {
882            _systemName.setEnabled(false);
883            _sysNameLabel.setEnabled(false);
884        } else {
885            _systemName.setEnabled(true);
886            _sysNameLabel.setEnabled(true);
887        }
888    }
889
890    /**
891     * Respond to the Cancel button in Add Logix window.
892     * <p>
893     * Note: Also get there if the user closes the Add Logix window.
894     *
895     * @param e The event heard
896     */
897    void cancelAddPressed(ActionEvent e) {
898        addLogixFrame.setVisible(false);
899        addLogixFrame.dispose();
900        addLogixFrame = null;
901        _inAddMode = false;
902        _inCopyMode = false;
903        if (f != null) {
904            f.setVisible(true);
905        }
906    }
907
908    /**
909     * Respond to the Copy Logix button in Add Logix window.
910     * <p>
911     * Provides a pane to set new properties of the copy.
912     *
913     * @param sName system name of Logix to be copied
914     */
915    void copyPressed(String sName) {
916        if (!checkFlags(sName)) {
917            return;
918        }
919        _showReminder = true;
920        // make an Add Logix Frame
921        if (addLogixFrame == null) {
922            JPanel panel5 = makeAddLogixFrame("TitleCopyLogix", "CopyLogixMessage",
923                    "package.jmri.jmrit.conditional.ConditionalCopy");    // NOI18N
924            // Create Logix
925            JButton create = new JButton(Bundle.getMessage("ButtonCopy"));  // NOI18N
926            panel5.add(create);
927            create.addActionListener(new CopyAction(sName));
928            addLogixFrame.pack();
929            addLogixFrame.setVisible(true);
930            _autoSystemName.setSelected(false);
931            addLogixFrame.setLocationRelativeTo(getFrame());
932            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
933                _autoSystemName.setSelected(prefMgr.getSimplePreferenceState(systemNameAuto));
934            });
935            _inCopyMode = true;
936        }
937        _curLogix = _logixManager.getBySystemName(sName);
938    }
939
940    class CopyAction implements ActionListener {
941        String _lgxName;
942        CopyAction(String lgxName) {
943            _lgxName = lgxName;
944        }
945        @Override
946        public void actionPerformed(ActionEvent e) {
947            copyLogixPressed(_lgxName);
948        }
949    }
950
951    /**
952     * Copy the Logix as configured in the Copy set up pane.
953     *
954     * @param lgxName Logix system name to be copied
955     */
956    private void copyLogixPressed(String lgxName) {
957        String sName = _systemName.getText();
958        String uName = _addUserName.getText();
959        if (uName.length() == 0) {
960            uName = null;
961        }
962        Logix targetLogix;
963        if (_autoSystemName.isSelected()) {
964            if (!checkLogixUserName(uName)) {
965                return;
966            }
967            targetLogix = _logixManager.createNewLogix(uName);
968        } else {
969            targetLogix = _logixManager.getBySystemName(sName);
970            if (targetLogix == null && uName != null) {
971                targetLogix = _logixManager.getByUserName(uName);
972            }
973            if (targetLogix != null) {
974                int result = JOptionPane.showConfirmDialog(f,
975                        Bundle.getMessage("ConfirmLogixDuplicate",
976                                targetLogix.getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME), lgxName), // NOI18N
977                        Bundle.getMessage("QuestionTitle"), JOptionPane.YES_NO_OPTION,    // NOI18N
978                        JOptionPane.QUESTION_MESSAGE);
979                if (JOptionPane.NO_OPTION == result) {
980                    return;
981                }
982            }
983            if (targetLogix == null) {
984                if (!checkLogixSysName()) {
985                    return;
986                }
987                // Create the new Logix
988                targetLogix = _logixManager.createNewLogix(sName, uName);
989                if (targetLogix == null) {
990                    // should never get here unless there is an assignment conflict
991                    log.error("Failure to create Logix with System Name: {}", sName);  // NOI18N
992                    return;
993                }
994            } else {
995                targetLogix.setUserName(uName);
996            }
997        }
998        cancelAddPressed(null);
999        _baseEdit = new ConditionalListCopy(lgxName, targetLogix);
1000        _baseEdit.locateAt(getFrame());
1001        _inCopyMode = true;
1002        _baseEdit.addLogixEventListener(new ConditionalBaseListener(lgxName));
1003    }
1004
1005    /**
1006     * Check and warn if a string is already in use as the user name of a Logix.
1007     *
1008     * @param uName the suggested name
1009     * @return true if not in use
1010     */
1011    boolean checkLogixUserName(String uName) {
1012        // check if a Logix with the same user name exists
1013        if (uName != null && uName.trim().length() > 0) {
1014            Logix x = _logixManager.getByUserName(uName);
1015            if (x != null) {
1016                // Logix with this user name already exists
1017                JOptionPane.showMessageDialog(getFrame(),
1018                        Bundle.getMessage("LogixError3"), Bundle.getMessage("ErrorTitle"),
1019                        JOptionPane.ERROR_MESSAGE);
1020                return false;
1021            }
1022        }
1023        return true;
1024    }
1025
1026    /**
1027     * Check for a valid Logix system name.
1028     * A valid name starts with the Logix prefix consisting of the Internal system prefix (normally I) + X,
1029     * and at least 1 additional character. The prefix will be added if necessary.
1030     * Any makeSystemName errors are logged to the system console and a dialog is displayed.
1031     * @return true if the name is now valid.
1032     */
1033    boolean checkLogixSysName() {
1034        String sName = _systemName.getText();
1035
1036        try {
1037            sName = InstanceManager.getDefault(jmri.LogixManager.class).makeSystemName(sName);
1038        } catch (jmri.NamedBean.BadSystemNameException ex) {
1039            JOptionPane.showMessageDialog(getFrame(),
1040                    Bundle.getMessage("LogixError8"), Bundle.getMessage("ErrorTitle"),
1041                    JOptionPane.ERROR_MESSAGE);
1042            return false;
1043        }
1044
1045        _systemName.setText(sName);
1046        return true;
1047    }
1048
1049    /**
1050     * Check if another Logix editing session is currently open or no system
1051     * name is provided.
1052     *
1053     * @param sName system name of Logix to be copied
1054     * @return true if a new session may be started
1055     */
1056    boolean checkFlags(String sName) {
1057        if (_inEditMode) {
1058            // Already editing a Logix, ask for completion of that edit
1059            JOptionPane.showMessageDialog(getFrame(),
1060                    Bundle.getMessage("LogixError32", _curLogix.getSystemName()),
1061                    Bundle.getMessage("ErrorTitle"),
1062                    JOptionPane.ERROR_MESSAGE);
1063            _baseEdit.bringToFront();
1064            return false;
1065        }
1066
1067        if (_inAddMode) {
1068            // Adding a Logix, ask for completion of that edit
1069            JOptionPane.showMessageDialog(getFrame(),
1070                    Bundle.getMessage("LogixError33"),
1071                    Bundle.getMessage("ErrorTitle"), // NOI18N
1072                    JOptionPane.ERROR_MESSAGE);
1073            addLogixFrame.toFront();
1074            return false;
1075        }
1076
1077        if (_inCopyMode) {
1078            // Already editing a Logix, ask for completion of that edit
1079            JOptionPane.showMessageDialog(getFrame(),
1080                    Bundle.getMessage("LogixError31", _curLogix.getSystemName()),
1081                    Bundle.getMessage("ErrorTitle"), // NOI18N
1082                    JOptionPane.ERROR_MESSAGE);
1083            _baseEdit.bringToFront();
1084            return false;
1085        }
1086
1087        if (sName != null) {
1088            // check if a Logix with this name exists
1089            Logix x = _logixManager.getBySystemName(sName);
1090            if (x == null) {
1091                // Logix does not exist, so cannot be edited
1092                log.error("No Logix with system name: {}", sName);
1093                JOptionPane.showMessageDialog(getFrame(),
1094                        Bundle.getMessage("LogixError5"),
1095                        Bundle.getMessage("ErrorTitle"), // NOI18N
1096                        JOptionPane.ERROR_MESSAGE);
1097                return false;
1098            }
1099        }
1100        return true;
1101    }
1102
1103    /**
1104     * Respond to the Create Logix button in Add Logix window.
1105     *
1106     * @param e The event heard
1107     */
1108    void createPressed(ActionEvent e) {
1109        // possible change
1110        _showReminder = true;
1111        String sName = "";
1112        String uName = _addUserName.getText();
1113        if (uName.length() == 0) {
1114            uName = null;
1115        }
1116        if (_autoSystemName.isSelected()) {
1117            if (!checkLogixUserName(uName)) {
1118                return;
1119            }
1120            _curLogix = _logixManager.createNewLogix(uName);
1121            sName = _curLogix.getSystemName();
1122        } else {
1123            if (!checkLogixSysName()) {
1124                return;
1125            }
1126            // Get validated system name
1127            sName = _systemName.getText();
1128            // check if a Logix with this name already exists
1129            Logix x = null;
1130            try {
1131                x = _logixManager.getBySystemName(sName);
1132            } catch (Exception ex) {
1133                // user input no good
1134                handleCreateException(sName);
1135                return;  // without creating
1136            }
1137            if (x != null) {
1138                // Logix already exists
1139                JOptionPane.showMessageDialog(getFrame(), Bundle.getMessage("LogixError1"),
1140                        Bundle.getMessage("ErrorTitle"), // NOI18N
1141                        JOptionPane.ERROR_MESSAGE);
1142                return;
1143            }
1144            if (!checkLogixUserName(uName)) {
1145                return;
1146            }
1147            // Create the new Logix
1148            _curLogix = _logixManager.createNewLogix(sName, uName);
1149            if (_curLogix == null) {
1150                // should never get here unless there is an assignment conflict
1151                log.error("Failure to create Logix with System Name: {}", sName);  // NOI18N
1152                return;
1153            }
1154        }
1155        cancelAddPressed(null);
1156        // create the Edit Logix Window
1157        editPressed(sName);
1158        InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
1159            prefMgr.setSimplePreferenceState(systemNameAuto, _autoSystemName.isSelected());
1160        });
1161    }
1162
1163    void handleCreateException(String sysName) {
1164        JOptionPane.showMessageDialog(getFrame(),
1165                Bundle.getMessage("ErrorLogixAddFailed", sysName), // NOI18N
1166                Bundle.getMessage("ErrorTitle"), // NOI18N
1167                JOptionPane.ERROR_MESSAGE);
1168    }
1169
1170    // ------------ Methods for Edit Logix Pane ------------
1171
1172    /**
1173     * Respond to the Edit button pressed in Logix table.
1174     *
1175     * @param sName system name of Logix to be edited
1176     */
1177    void editPressed(String sName) {
1178        if (!checkFlags(sName)) {
1179            return;
1180        }
1181
1182        if (sName.equals(SensorGroupFrame.logixSysName)) {
1183            // Sensor group message
1184            JOptionPane.showMessageDialog(getFrame(),
1185                    Bundle.getMessage("LogixWarn8", SensorGroupFrame.logixUserName, SensorGroupFrame.logixSysName),
1186                    Bundle.getMessage("WarningTitle"), // NOI18N
1187                    JOptionPane.WARNING_MESSAGE);
1188            return;
1189        }
1190        _curLogix = _logixManager.getBySystemName(sName);
1191
1192        // Create a new conditional edit view, add the listener.
1193        if (_editMode == EditMode.TREEEDIT) {
1194            _baseEdit = new ConditionalTreeEdit(sName);
1195        } else {
1196            _baseEdit = new ConditionalListEdit(sName);
1197        }
1198        _baseEdit.locateAt(getFrame());
1199        _inEditMode = true;
1200        _baseEdit.addLogixEventListener(new ConditionalBaseListener(sName));
1201    }
1202
1203    class ConditionalBaseListener implements ConditionalEditBase.LogixEventListener {
1204        String _lgxName;
1205        ConditionalBaseListener(String lgxName) {
1206            _lgxName = lgxName;
1207        }
1208
1209        @Override
1210        public void logixEventOccurred() {
1211            _baseEdit.logixData.forEach((key, value) -> {
1212                if (key.equals("Finish")) {                  // NOI18N
1213                    _baseEdit = null;
1214                    _inEditMode = false;
1215                    _inCopyMode = false;
1216                    Logix x = _logixManager.getBySystemName(value);
1217                    if (x == null) {
1218                        log.error("Found no logix for name {} when done", value);
1219                        return;
1220                    }
1221                    x.activateLogix();
1222                    f.setVisible(true);
1223                } else if (key.equals("Delete")) {           // NOI18N
1224                    deletePressed(value);
1225                } else if (key.equals("chgUname")) {         // NOI18N
1226                    Logix x = _logixManager.getBySystemName(_lgxName);
1227                    if (x == null) {
1228                        log.error("Found no logix for name {} when changing user name (2)", _lgxName);
1229                        return;
1230                    }
1231                    x.setUserName(value);
1232                    m.fireTableDataChanged();
1233                }
1234            });
1235        }
1236    }
1237
1238    /**
1239     * Display reminder to save.
1240     */
1241    void showSaveReminder() {
1242        if (_showReminder) {
1243            if (InstanceManager.getNullableDefault(jmri.UserPreferencesManager.class) != null) {
1244                InstanceManager.getDefault(jmri.UserPreferencesManager.class).
1245                        showInfoMessage(Bundle.getMessage("ReminderTitle"), Bundle.getMessage("ReminderSaveString", Bundle.getMessage("MenuItemLogixTable")), // NOI18N
1246                                getClassName(),
1247                                "remindSaveLogix");  // NOI18N
1248            }
1249        }
1250    }
1251
1252    @Override
1253    public void setMessagePreferencesDetails() {
1254        HashMap<Integer, String> options = new HashMap< Integer, String>(3);
1255        options.put(0x00, Bundle.getMessage("DeleteAsk"));      // NOI18N
1256        options.put(0x01, Bundle.getMessage("DeleteNever"));    // NOI18N
1257        options.put(0x02, Bundle.getMessage("DeleteAlways"));   // NOI18N
1258        jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class).setMessageItemDetails(getClassName(), "delete", Bundle.getMessage("DeleteLogix"), options, 0x00);  // NOI18N
1259        jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails(getClassName(), "remindSaveLogix", Bundle.getMessage("HideSaveReminder"));  // NOI18N
1260        super.setMessagePreferencesDetails();
1261    }
1262
1263    /**
1264     * Respond to the Delete combo selection Logix window or conditional view
1265     * delete request.
1266     *
1267     * @param sName system name of bean to be deleted
1268     */
1269    void deletePressed(String sName) {
1270        if (!checkConditionalReferences(sName)) {
1271            return;
1272        }
1273        final Logix x = _logixManager.getBySystemName(sName);
1274        final jmri.UserPreferencesManager p;
1275        p = jmri.InstanceManager.getNullableDefault(jmri.UserPreferencesManager.class);
1276        if (p != null && p.getMultipleChoiceOption(getClassName(), "delete") == 0x02) {     // NOI18N
1277            if (x != null) {
1278                _logixManager.deleteLogix(x);
1279                deleteSourceWhereUsed();
1280            }
1281        } else {
1282            final JDialog dialog = new JDialog();
1283            String msg;
1284            dialog.setTitle(Bundle.getMessage("QuestionTitle"));     // NOI18N
1285            dialog.setLocationRelativeTo(getFrame());
1286            dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
1287            JPanel container = new JPanel();
1288            container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
1289            container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
1290            msg = Bundle.getMessage("ConfirmLogixDelete", sName);    // NOI18N
1291            JLabel question = new JLabel(msg);
1292            question.setAlignmentX(Component.CENTER_ALIGNMENT);
1293            container.add(question);
1294
1295            final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));  // NOI18N
1296            remember.setFont(remember.getFont().deriveFont(10f));
1297            remember.setAlignmentX(Component.CENTER_ALIGNMENT);
1298
1299            JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));    // NOI18N
1300            JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));      // NOI18N
1301            JPanel button = new JPanel();
1302            button.setAlignmentX(Component.CENTER_ALIGNMENT);
1303            button.add(yesButton);
1304            button.add(noButton);
1305            container.add(button);
1306
1307            noButton.addActionListener(new ActionListener() {
1308                @Override
1309                public void actionPerformed(ActionEvent e) {
1310                    //there is no point in remebering this the user will never be
1311                    //able to delete a bean!
1312                    /*if(remember.isSelected()){
1313                     setDisplayDeleteMsg(0x01);
1314                     }*/
1315                    dialog.dispose();
1316                }
1317            });
1318
1319            yesButton.addActionListener(new ActionListener() {
1320                @Override
1321                public void actionPerformed(ActionEvent e) {
1322                    if (p != null && remember.isSelected()) {
1323                        p.setMultipleChoiceOption(getClassName(), "delete", 0x02);  // NOI18N
1324                    }
1325                    if (x != null) {
1326                        _logixManager.deleteLogix(x);
1327                        deleteSourceWhereUsed();
1328                    }
1329                    dialog.dispose();
1330                }
1331            });
1332            container.add(remember);
1333            container.setAlignmentX(Component.CENTER_ALIGNMENT);
1334            container.setAlignmentY(Component.CENTER_ALIGNMENT);
1335            dialog.getContentPane().add(container);
1336            dialog.pack();
1337            dialog.setModal(true);
1338            dialog.setVisible(true);
1339        }
1340
1341        f.setVisible(true);
1342    }
1343
1344    /**
1345     * Build a tree set from conditional references.
1346     *
1347     * @since 4.7.4
1348     * @param varList The ConditionalVariable list that might contain
1349     *                conditional references
1350     * @param treeSet A tree set to be built from the varList data
1351     */
1352    void loadReferenceNames(List<ConditionalVariable> varList, TreeSet<String> treeSet) {
1353        treeSet.clear();
1354        for (ConditionalVariable var : varList) {
1355            if (var.getType() == Conditional.Type.CONDITIONAL_TRUE
1356                    || var.getType() == Conditional.Type.CONDITIONAL_FALSE) {
1357                treeSet.add(var.getName());
1358            }
1359        }
1360    }
1361
1362    boolean checkConditionalUserName(String uName, Logix logix) {
1363        if ((uName != null) && (!(uName.equals("")))) {
1364            Conditional p = _conditionalManager.getByUserName(logix, uName);
1365            if (p != null) {
1366                // Conditional with this user name already exists
1367                log.error("Failure to update Conditional with Duplicate User Name: {}", uName);
1368                JOptionPane.showMessageDialog(getFrame(),
1369                        Bundle.getMessage("LogixError10"), // NOI18N
1370                        Bundle.getMessage("ErrorTitle"), // NOI18N
1371                        JOptionPane.ERROR_MESSAGE);
1372                return false;
1373            }
1374        }
1375        return true;
1376    }
1377
1378    /**
1379     * Check form of Conditional systemName.
1380     *
1381     * @param sName system name of bean to be checked
1382     * @return false if sName is empty string or null
1383     */
1384    boolean checkConditionalSystemName(String sName) {
1385        if ((sName != null) && (!(sName.equals("")))) {
1386            Conditional p = _conditionalManager.getBySystemName(sName);
1387            if (p != null) {
1388                return false;
1389            }
1390        } else {
1391            return false;
1392        }
1393        return true;
1394    }
1395
1396    /**
1397     * Check for conditional references.
1398     *
1399     * @since 4.7.4
1400     * @param logixName The Logix under consideration
1401     * @return true if no references
1402     */
1403    boolean checkConditionalReferences(String logixName) {
1404        _saveTargetList.clear();
1405        Logix x = _logixManager.getLogix(logixName);
1406        int numConditionals = x.getNumConditionals();
1407        if (numConditionals > 0) {
1408            for (int i = 0; i < numConditionals; i++) {
1409                String csName = x.getConditionalByNumberOrder(i);
1410
1411                // If the conditional is a where used source, retain it for later
1412                ArrayList<String> targetList = InstanceManager.getDefault(jmri.ConditionalManager.class).getTargetList(csName);
1413                if (targetList.size() > 0) {
1414                    _saveTargetList.put(csName, targetList);
1415                }
1416
1417                // If the conditional is a where used target, check scope
1418                ArrayList<String> refList = InstanceManager.getDefault(jmri.ConditionalManager.class).getWhereUsed(csName);
1419                if (refList != null) {
1420                    for (String refName : refList) {
1421                        Logix xRef = _conditionalManager.getParentLogix(refName);
1422                        String xsName = xRef.getSystemName();
1423                        if (logixName.equals(xsName)) {
1424                            // Member of the same Logix
1425                            continue;
1426                        }
1427
1428                        // External references have to be removed before the Logix can be deleted.
1429                        Conditional c = x.getConditional(csName);
1430                        Conditional cRef = xRef.getConditional(refName);
1431                        JOptionPane.showMessageDialog(getFrame(),
1432                                Bundle.getMessage("LogixError11", c.getUserName(), c.getSystemName(), cRef.getUserName(),
1433                                        cRef.getSystemName(), xRef.getUserName(), xRef.getSystemName()), // NOI18N
1434                                Bundle.getMessage("ErrorTitle"),
1435                                JOptionPane.ERROR_MESSAGE);  // NOI18N
1436                        return false;
1437                    }
1438                }
1439            }
1440        }
1441        return true;
1442    }
1443
1444    /**
1445     * Remove target/source where used entries after a Logix delete.
1446     *
1447     * @since 4.7.4
1448     */
1449    void deleteSourceWhereUsed() {
1450        _saveTargetList.forEach((refName, targetList) -> {
1451            for (String targetName : targetList) {
1452                InstanceManager.getDefault(jmri.ConditionalManager.class).removeWhereUsed(targetName, refName);
1453            }
1454        });
1455    }
1456
1457    /**
1458     * Update the conditional reference where used.
1459     * <p>
1460     * The difference between the saved target names and new target names is
1461     * used to add/remove where used references.
1462     *
1463     * @since 4.7.4
1464     * @param newTargetNames The conditional target names after updating
1465     * @param refName        The system name for the referencing conditional
1466     */
1467    void updateWhereUsed(TreeSet<String> newTargetNames, String refName) {
1468        TreeSet<String> deleteNames = new TreeSet<>(_saveTargetNames);
1469        deleteNames.removeAll(newTargetNames);
1470        for (String deleteName : deleteNames) {
1471            InstanceManager.getDefault(jmri.ConditionalManager.class).removeWhereUsed(deleteName, refName);
1472        }
1473
1474        TreeSet<String> addNames = new TreeSet<>(newTargetNames);
1475        addNames.removeAll(_saveTargetNames);
1476        for (String addName : addNames) {
1477            InstanceManager.getDefault(jmri.ConditionalManager.class).addWhereUsed(addName, refName);
1478        }
1479    }
1480
1481    /**
1482     * Create Variable and Action editing pane center part.
1483     *
1484     * @param comp  Field or comboBox to include on sub pane
1485     * @param label property key for label
1486     * @param hint  property key for tooltip for this sub pane
1487     * @return JPanel containing interface
1488     */
1489    JPanel makeEditPanel(JComponent comp, String label, String hint) {
1490        JPanel panel = new JPanel();
1491        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
1492        JPanel p = new JPanel();
1493        p.add(new JLabel(Bundle.getMessage(label)));
1494        panel.add(p);
1495        if (hint != null) {
1496            panel.setToolTipText(Bundle.getMessage(hint));
1497        }
1498        comp.setMaximumSize(comp.getPreferredSize());  // override for text fields
1499        panel.add(comp);
1500        panel.add(Box.createVerticalGlue());
1501        return panel;
1502    }
1503
1504    /**
1505     * Format time to hh:mm given integer hour and minute.
1506     *
1507     * @param hour   value for time hours
1508     * @param minute value for time minutes
1509     * @return Formatted time string
1510     */
1511    public static String formatTime(int hour, int minute) {
1512        String s = "";
1513        String t = Integer.toString(hour);
1514        if (t.length() == 2) {
1515            s = t + ":";
1516        } else if (t.length() == 1) {
1517            s = "0" + t + ":";
1518        }
1519        t = Integer.toString(minute);
1520        if (t.length() == 2) {
1521            s = s + t;
1522        } else if (t.length() == 1) {
1523            s = s + "0" + t;
1524        }
1525        if (s.length() != 5) {
1526            // input error
1527            s = "00:00";
1528        }
1529        return s;
1530    }
1531
1532    @Override
1533    public String getClassDescription() {
1534        return Bundle.getMessage("TitleLogixTable");        // NOI18N
1535    }
1536
1537    @Override
1538    protected String getClassName() {
1539        return LogixTableAction.class.getName();
1540    }
1541
1542    // ------------ Methods for Conditional References Window ------------
1543    /**
1544     * Builds the conditional references window when the Conditional Variable
1545     * References menu item is selected.
1546     * <p>
1547     * This is a stand-alone window that can be closed at any time.
1548     *
1549     * @since 4.7.4
1550     */
1551    void makeWhereUsedWindow() {
1552
1553        JmriJFrame referenceListFrame = new JmriJFrame(Bundle.getMessage("LabelRefTitle"), false, true);    // NOI18N
1554        Container contentPane = referenceListFrame.getContentPane();
1555        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
1556
1557        // build header information
1558        JPanel panel1 = new JPanel();
1559        panel1.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 5));
1560        panel1.add(new JLabel(Bundle.getMessage("LabelRefTarget")));    // NOI18N
1561        panel1.add(new JLabel(Bundle.getMessage("LabelRefSource")));    // NOI18N
1562        contentPane.add(panel1);
1563
1564        // Build the conditional references listing
1565        JScrollPane scrollPane = null;
1566        JTextArea textContent = buildWhereUsedListing();
1567        scrollPane = new JScrollPane(textContent);
1568        contentPane.add(scrollPane);
1569
1570        referenceListFrame.pack();
1571        referenceListFrame.setVisible(true);
1572    }
1573
1574    /**
1575     * Creates a component containing the conditional reference where used list.
1576     * The source is {@link jmri.ConditionalManager#getWhereUsedMap()}
1577     *
1578     * @return a TextArea, empty if reference is not used
1579     * @since 4.7.4
1580     */
1581    public JTextArea buildWhereUsedListing() {
1582        JTextArea condText = new javax.swing.JTextArea();
1583        condText.setText(null);
1584        HashMap<String, ArrayList<String>> whereUsed = InstanceManager.getDefault(ConditionalManager.class).getWhereUsedMap();
1585        SortedSet<String> targets = new TreeSet<>(whereUsed.keySet());
1586        targets.forEach((target) -> {
1587            condText.append("\n" + target + "\t" + getWhereUsedName(target) + "  \n");
1588            ArrayList<String> refNames = whereUsed.get(target);
1589            refNames.forEach((refName) -> {
1590                condText.append("\t\t" + refName + "\t" + getWhereUsedName(refName) + "  \n");
1591            });
1592        });
1593        condText.setCaretPosition(0);
1594        condText.setTabSize(2);
1595        condText.setEditable(false);
1596        return condText;
1597    }
1598
1599    String getWhereUsedName(String cName) {
1600        Conditional cond = _conditionalManager.getBySystemName(cName);
1601        if ( cond!=null){
1602            return cond.getUserName();
1603        }
1604        return "";
1605    }
1606
1607// ------------ Methods for Conditional Browser Window ------------
1608    /**
1609     * Respond to the Browse button pressed in Logix table.
1610     *
1611     * @param sName The selected Logix system name
1612     */
1613    void browserPressed(String sName) {
1614        makeBrowserWindow(sName);
1615    }
1616
1617    /**
1618     * Create and initialize the conditionals browser window.
1619     * @param lgxName Logix system name
1620     */
1621    void makeBrowserWindow(String lgxName) {
1622        Logix logix = _logixManager.getBySystemName(lgxName);
1623        if (logix == null) {
1624            return;
1625        }
1626            // Logix was found, create the window
1627        JmriJFrame condBrowserFrame = new JmriJFrame(Bundle.getMessage("BrowserTitle"), false, true);   // NOI18N
1628        condBrowserFrame.addHelpMenu("package.jmri.jmrit.beantable.LogixAddEdit", true);            // NOI18N
1629
1630        Container contentPane = condBrowserFrame.getContentPane();
1631        contentPane.setLayout(new BorderLayout());
1632
1633        // LOGIX header information
1634        JPanel topPanel = new JPanel();
1635        String tStr = Bundle.getMessage("BrowserLogix") + " " + logix.getSystemName() + "    " // NOI18N
1636                + logix.getUserName() + "    "
1637                + (Boolean.valueOf(logix.getEnabled())
1638                        ? Bundle.getMessage("BrowserEnabled") // NOI18N
1639                        : Bundle.getMessage("BrowserDisabled"));  // NOI18N
1640        topPanel.add(new JLabel(tStr));
1641        contentPane.add(topPanel, BorderLayout.NORTH);
1642
1643        // Build the conditionals listing
1644        JScrollPane scrollPane = null;
1645        JTextArea textContent = buildConditionalListing(logix);
1646        scrollPane = new JScrollPane(textContent);
1647        contentPane.add(scrollPane);
1648
1649        JPanel bottomPanel = new JPanel();
1650        bottomPanel.setLayout(new BorderLayout());
1651        JButton helpBrowse = new JButton(Bundle.getMessage("MenuHelp"));   // NOI18N
1652        bottomPanel.add(helpBrowse, BorderLayout.WEST);
1653        helpBrowse.addActionListener(new ActionListener() {
1654            @Override
1655            public void actionPerformed(ActionEvent e) {
1656                JOptionPane.showMessageDialog(condBrowserFrame,
1657                        Bundle.getMessage("BrowserHelpText"),   // NOI18N
1658                        Bundle.getMessage("BrowserHelpTitle"),  // NOI18N
1659                        JOptionPane.INFORMATION_MESSAGE);
1660            }
1661        });
1662        JButton saveBrowse = new JButton(Bundle.getMessage("BrowserSaveButton"));   // NOI18N
1663        saveBrowse.setToolTipText(Bundle.getMessage("BrowserSaveButtonHint"));      // NOI18N
1664        bottomPanel.add(saveBrowse, BorderLayout.EAST);
1665        saveBrowse.addActionListener(new SaveAction(lgxName));
1666        contentPane.add(bottomPanel, BorderLayout.SOUTH);
1667
1668        condBrowserFrame.pack();
1669        condBrowserFrame.setVisible(true);
1670    }  // makeBrowserWindow
1671
1672    class SaveAction implements ActionListener {
1673        String _lgxName;
1674        SaveAction(String lgxName) {
1675            _lgxName = lgxName;
1676        }
1677        @Override
1678        public void actionPerformed(ActionEvent e) {
1679            saveBrowserPressed(_lgxName);
1680        }
1681    }
1682
1683    /**
1684     * Save the Logix browser window content to a text file.
1685     * @param lgxName Logix system name
1686     */
1687    void saveBrowserPressed(String lgxName) {
1688        Logix logix = _logixManager.getBySystemName(lgxName);
1689        if (logix == null) {
1690            log.warn("Can't save browsed data, logix {} no longer exits", lgxName);
1691            return;
1692        }
1693        JFileChooser userFileChooser = new JFileChooser(FileUtil.getUserFilesPath());
1694        userFileChooser.setApproveButtonText(Bundle.getMessage("BrowserSaveDialogApprove"));  // NOI18N
1695        userFileChooser.setDialogTitle(Bundle.getMessage("BrowserSaveDialogTitle"));  // NOI18N
1696        userFileChooser.rescanCurrentDirectory();
1697        // Default to logix system name.txt
1698        userFileChooser.setSelectedFile(new File(FileUtil.sanitizeFilename(logix.getSystemName()) + ".txt"));  // NOI18N
1699        int retVal = userFileChooser.showSaveDialog(null);
1700        if (retVal != JFileChooser.APPROVE_OPTION) {
1701            log.debug("Save browser content stopped, no file selected");  // NOI18N
1702            return;  // give up if no file selected or cancel pressed
1703        }
1704        File file = userFileChooser.getSelectedFile();
1705        log.debug("Save browser content to '{}'", file);  // NOI18N
1706
1707        if (file.exists()) {
1708            Object[] options = {Bundle.getMessage("BrowserSaveDuplicateReplace"),  // NOI18N
1709                    Bundle.getMessage("BrowserSaveDuplicateAppend"),  // NOI18N
1710                    Bundle.getMessage("ButtonCancel")};               // NOI18N
1711            int selectedOption = JOptionPane.showOptionDialog(null,
1712                    Bundle.getMessage("BrowserSaveDuplicatePrompt", file.getName()), // NOI18N
1713                    Bundle.getMessage("BrowserSaveDuplicateTitle"),   // NOI18N
1714                    JOptionPane.DEFAULT_OPTION,
1715                    JOptionPane.WARNING_MESSAGE,
1716                    null, options, options[0]);
1717            if (selectedOption == 2 || selectedOption == -1) {
1718                log.debug("Save browser content stopped, file replace/append cancelled");  // NOI18N
1719                return;  // Cancel selected or dialog box closed
1720            }
1721            if (selectedOption == 0) {
1722                FileUtil.delete(file);  // Replace selected
1723            }
1724        }
1725
1726        // Create the file content
1727        String tStr = Bundle.getMessage("BrowserLogix") + " " + logix.getSystemName() + "    "  // NOI18N
1728                + logix.getUserName() + "    "
1729                + (Boolean.valueOf(logix.getEnabled())
1730                        ? Bundle.getMessage("BrowserEnabled")    // NOI18N
1731                        : Bundle.getMessage("BrowserDisabled"));  // NOI18N
1732        JTextArea textContent = buildConditionalListing(logix);
1733        try {
1734            // ADD Logix Header inforation first
1735            FileUtil.appendTextToFile(file, tStr);
1736            FileUtil.appendTextToFile(file, textContent.getText());
1737        } catch (IOException e) {
1738            log.error("Unable to write browser content to '{}', exception: '{}'", file, e);  // NOI18N
1739        }
1740    }
1741
1742    /**
1743     * Builds a Component representing the current conditionals for the selected
1744     * Logix statement.
1745     *
1746     *@param logix browsing Logix
1747     * @return a TextArea listing existing conditionals; will be empty if there
1748     *         are none
1749     */
1750    JTextArea buildConditionalListing(Logix logix) {
1751        String showSystemName,
1752                showCondName,
1753                condName,
1754                operand,
1755                tStr;
1756
1757        List<ConditionalVariable> variableList;
1758        List<ConditionalAction> actionList;
1759        ConditionalVariable variable;
1760        ConditionalAction action;
1761        String _antecedent = null;
1762
1763        JTextArea condText = new javax.swing.JTextArea();
1764        condText.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
1765        condText.setText(null);
1766        int numConditionals = logix.getNumConditionals();
1767        for (int rx = 0; rx < numConditionals; rx++) {
1768            conditionalRowNumber = rx;
1769            Conditional curConditional = _conditionalManager.getBySystemName(logix.getConditionalByNumberOrder(rx));
1770            if (curConditional==null){
1771                continue;
1772            }
1773            variableList = curConditional.getCopyOfStateVariables();
1774            actionList = curConditional.getCopyOfActions();
1775
1776            showCondName = curConditional.getUserName();
1777            if (showCondName == null) {
1778                showCondName = "";
1779            }
1780            showSystemName = curConditional.getSystemName();
1781
1782            // If no user name for a conditional, create one using C + row number
1783            if (showCondName.equals("")) {
1784                showCondName = "C" + (rx + 1);
1785            }
1786            condText.append("\n  " + showSystemName + "  " + showCondName + "   \n");
1787            if (curConditional.getLogicType() == Conditional.AntecedentOperator.MIXED) {
1788                _antecedent = curConditional.getAntecedentExpression();
1789                String antecedent = ConditionalEditBase.translateAntecedent(_antecedent, false);
1790                condText.append("   " + Bundle.getMessage("LogixAntecedent") + " " + antecedent + "  \n");   // NOI18N
1791            }
1792
1793            for (int i = 0; i < variableList.size(); i++) {
1794                variable = variableList.get(i);
1795                String varTrigger = (variable.doTriggerActions())
1796                        ? "[x]" // NOI18N
1797                        : "[ ]";
1798                tStr = "    " + varTrigger + " ";
1799                tStr = tStr + " R" + (i + 1) + (i > 8 ? " " : "  ");  // Makes {Rx}bb or {Rxx}b
1800                condText.append(tStr);
1801
1802                operand = variable.getOpernString();
1803                if (i == 0) { // add the IF to the first conditional
1804                    condText.append(Bundle.getMessage("BrowserIF") + " " + operand + " ");    // NOI18N
1805                } else {
1806                    condText.append("  " + operand + " ");
1807                }
1808                if (variable.isNegated()) {
1809                    condText.append(Bundle.getMessage("LogicNOT") + " ");     // NOI18N
1810                }
1811                condText.append(variable.toString() + "   \n");
1812            } // for _variableList
1813
1814            if (actionList.size() > 0) {
1815                condText.append("             " + Bundle.getMessage("BrowserTHEN") + "   \n");  // NOI18N
1816                boolean triggerType = curConditional.getTriggerOnChange();
1817                for (int i = 0; i < actionList.size(); i++) {
1818                    action = actionList.get(i);
1819                    condName = action.description(triggerType);
1820                    condText.append("               " + condName + "   \n");
1821                }  // for _actionList
1822            } else {
1823                condText.append("             " + Bundle.getMessage("BrowserNoAction") + "   \n\n");    // NOI18N
1824            }
1825        } // for numConditionals
1826
1827        condText.setCaretPosition(0);
1828        condText.setTabSize(4);
1829        condText.setEditable(false);
1830        return condText;
1831    }  // buildConditionalListing
1832
1833    private final static Logger log = LoggerFactory.getLogger(LogixTableAction.class);
1834
1835}