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