001package jmri.jmrit.beantable;
002
003import java.awt.*;
004import java.awt.event.*;
005import java.io.File;
006import java.io.IOException;
007import java.util.*;
008import java.util.List;
009
010import javax.annotation.Nonnull;
011import javax.swing.*;
012import javax.swing.table.TableColumn;
013
014import jmri.InstanceManager;
015import jmri.Manager;
016import jmri.NamedBean;
017import jmri.NamedBean.BadSystemNameException;
018import jmri.NamedBean.BadUserNameException;
019import jmri.UserPreferencesManager;
020import jmri.jmrit.logixng.Base;
021import jmri.jmrit.logixng.tools.swing.AbstractLogixNGEditor;
022import jmri.jmrit.logixng.tools.swing.DeleteBean;
023import jmri.util.FileUtil;
024import jmri.util.JmriJFrame;
025import jmri.util.swing.JmriJOptionPane;
026
027/**
028 * Swing action to create and register a LogixNG Table.
029 * <p>
030 Also contains the panes to create, edit, and delete a LogixNG.
031 <p>
032 * Most of the text used in this GUI is in BeanTableBundle.properties, accessed
033 * via Bundle.getMessage().
034 *
035 * @author Dave Duchamp Copyright (C) 2007 (LogixTableAction)
036 * @author Pete Cressman Copyright (C) 2009, 2010, 2011 (LogixTableAction)
037 * @author Matthew Harris copyright (c) 2009 (LogixTableAction)
038 * @author Dave Sand copyright (c) 2017 (LogixTableAction)
039 * @author Daniel Bergqvist copyright (c) 2019 (AbstractLogixNGTableEditor)
040 * @author Dave Sand copyright (c) 2021 (AbstractLogixNGTableEditor)
041 *
042 * @param <E> the type of NamedBean supported by this model
043 */
044public abstract class AbstractLogixNGTableAction<E extends NamedBean> extends AbstractTableAction<E> {
045
046
047    private static final ResourceBundle rbx = ResourceBundle.getBundle("jmri.jmrit.logixng.LogixNGBundle");
048    private static final ResourceBundle rbx2 = ResourceBundle.getBundle("jmri.jmrit.logixng.tools.swing.LogixNGSwingBundle");
049
050    // Browser Options
051    static final String PRINT_LINE_NUMBERS_OPTION = "jmri.jmrit.logixng.PrintLineNumbers";
052    static final String PRINT_ERROR_HANDLING_OPTION = "jmri.jmrit.logixng.ErrorHandling";
053    static final String PRINT_NOT_CONNECTED_OPTION = "jmri.jmrit.logixng.NotConnectedSockets";
054    static final String PRINT_LOCAL_VARIABLES_OPTION = "jmri.jmrit.logixng.LocalVariables";
055    static final String PRINT_SYSTEM_NAMES_OPTION = "jmri.jmrit.logixng.SystemNames";
056
057    JTextArea _textContent;
058    DeleteBean<E> deleteBean = new DeleteBean<>(getManager());
059
060    /**
061     * Create a AbstractLogixNGTableAction instance.
062     *
063     * @param s the Action title, not the title of the resulting frame. Perhaps
064     *          this should be changed?
065     */
066    public AbstractLogixNGTableAction(String s) {
067        super(s);
068        super.setEnabled(false);
069    }
070
071    protected abstract AbstractLogixNGEditor<E> getEditor(BeanTableDataModel<E> m, String sName);
072
073    protected boolean isEditSupported() {
074        return true;
075    }
076
077    @Nonnull
078    @Override
079    protected abstract Manager<E> getManager();
080
081    protected abstract void enableAll(boolean enable);
082
083    protected abstract void setEnabled(E bean, boolean enable);
084
085    protected abstract boolean isEnabled(E bean);
086
087    protected abstract E createBean(String userName);
088
089    protected abstract E createBean(String systemName, String userName);
090
091    protected abstract void deleteBean(E bean);
092
093    protected boolean browseMonoSpace() { return false; }
094
095    protected abstract String getBeanText(E bean);
096
097    protected abstract String getBrowserTitle();
098
099    protected abstract String getAddTitleKey();
100
101    protected abstract String getCreateButtonHintKey();
102
103    protected abstract void getListenerRefsIncludingChildren(E t, List<String> list);
104
105    protected abstract boolean hasChildren(E t);
106
107    // ------------ Methods for LogixNG Table Window ------------
108
109    /**
110     * Create the JTable DataModel, along with the changes (overrides of
111     * BeanTableDataModel) for the specific case of a LogixNG table.
112     */
113    @Override
114    protected void createModel() {
115        m = new TableModel();
116    }
117
118    /**
119     * Set title of NamedBean table.
120     */
121    @Override
122    protected void setTitle() {
123        f.setTitle(Bundle.getMessage("TitleLogixNGTable"));
124    }
125
126    /**
127     * Insert 2 table specific menus.
128     * <p>
129     * Accounts for the Window and Help menus, which are already added to the
130     * menu bar as part of the creation of the JFrame, by adding the new menus 2
131     * places earlier unless the table is part of the ListedTableFrame, which
132     * adds the Help menu later on.
133     *
134     * @param f the JFrame of this table
135     */
136    @Override
137    public void setMenuBar(BeanTableFrame<E> f) {
138        JMenu menu = new JMenu(Bundle.getMessage("MenuOptions"));  // NOI18N
139        menu.setMnemonic(KeyEvent.VK_O);
140        javax.swing.JMenuBar menuBar = f.getJMenuBar();
141        int pos = menuBar.getMenuCount() - 1;  // count the number of menus to insert the TableMenus before 'Window' and 'Help'
142        int offset = 1;
143        log.debug("setMenuBar number of menu items = {}", pos);  // NOI18N
144        for (int i = 0; i <= pos; i++) {
145            if (menuBar.getComponent(i) instanceof JMenu) {
146                if (((JMenu) menuBar.getComponent(i)).getText().equals(Bundle.getMessage("MenuHelp"))) {  // NOI18N
147                    offset = -1;  // correct for use as part of ListedTableAction where the Help Menu is not yet present
148                }
149            }
150        }
151
152        // Do not include this menu for Module or Table tables
153        if (this instanceof LogixNGTableAction) {
154            JMenuItem r = new JMenuItem(Bundle.getMessage("EnableAllLogixNGs"));  // NOI18N
155            r.addActionListener((ActionEvent e) -> {
156                enableAll(true);
157            });
158            menu.add(r);
159
160            r = new JMenuItem(Bundle.getMessage("DisableAllLogixNGs"));  // NOI18N
161            r.addActionListener((ActionEvent e) -> {
162                enableAll(false);
163            });
164            menu.add(r);
165
166            menuBar.add(menu, pos + offset);
167            offset++;
168        }
169
170        menu = new JMenu(Bundle.getMessage("MenuTools"));  // NOI18N
171        menu.setMnemonic(KeyEvent.VK_T);
172
173        JMenuItem item = new JMenuItem(rbx2.getString("MenuOpenClipboard"));  // NOI18N
174        item.addActionListener((ActionEvent e) -> {
175            jmri.jmrit.logixng.tools.swing.TreeEditor.openClipboard();
176        });
177        menu.add(item);
178
179        item = new JMenuItem(Bundle.getMessage("OpenPickListTables"));  // NOI18N
180        item.addActionListener((ActionEvent e) -> {
181            openPickListTable();
182        });
183        menu.add(item);
184
185        menuBar.add(menu, pos + offset);  // add this menu to the right of the previous
186    }
187
188    /**
189     * Open a new Pick List to drag Actions from to form NamedBean.
190     */
191    private void openPickListTable() {
192        if (_pickTables == null) {
193            _pickTables = new jmri.jmrit.picker.PickFrame(Bundle.getMessage("TitlePickList"));  // NOI18N
194        } else {
195            _pickTables.setVisible(true);
196        }
197        _pickTables.toFront();
198    }
199
200    @Override
201    protected String helpTarget() {
202        return "package.jmri.jmrit.beantable.LogixNGTable";  // NOI18N
203    }
204
205    // ------------ variable definitions ------------
206
207    protected AbstractLogixNGEditor<E> _editor = null;
208
209    boolean _showReminder = false;
210    private boolean _checkEnabled = jmri.InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).isStoreCheckEnabled();
211    jmri.jmrit.picker.PickFrame _pickTables;
212
213    // Current focus variables
214    protected E _curNamedBean = null;
215    int conditionalRowNumber = 0;
216
217    protected final Base.PrintTreeSettings _printTreeSettings = new Base.PrintTreeSettings();
218
219    // Add E Variables
220    JmriJFrame addLogixNGFrame = null;
221    JTextField _systemName = new JTextField(20);
222    JTextField _addUserName = new JTextField(20);
223    JCheckBox _autoSystemName = new JCheckBox(Bundle.getMessage("LabelAutoSysName"));   // NOI18N
224    JLabel _sysNameLabel = new JLabel(rbx.getString("BeanNameLogixNG") + " " + Bundle.getMessage("ColumnSystemName") + ":");  // NOI18N
225    JLabel _userNameLabel = new JLabel(rbx.getString("BeanNameLogixNG") + " " + Bundle.getMessage("ColumnUserName") + ":");   // NOI18N
226    String systemNameAuto = this.getClassName() + ".AutoSystemName";       // NOI18N
227    JButton create;
228
229    // Edit E Variables
230    private boolean _inEditMode = false;
231    private boolean _inCopyMode = false;
232
233    // ------------ Methods for Add bean Window ------------
234
235    /**
236     * Respond to the Add button in bean table Creates and/or initialize
237     * the Add bean pane.
238     *
239     * @param e The event heard
240     */
241    @Override
242    protected void addPressed(ActionEvent e) {
243        // possible change
244        if (!checkFlags(null)) {
245            return;
246        }
247        _showReminder = true;
248        // make an Add bean Frame
249        if (addLogixNGFrame == null) {
250            String titleKey = getAddTitleKey();
251            String buttonHintKey = getCreateButtonHintKey();
252            JPanel panel5 = makeAddFrame(titleKey, "Add");  // NOI18N
253            // Create bean
254            create = new JButton(Bundle.getMessage("ButtonCreate"));  // NOI18N
255            panel5.add(create);
256            create.addActionListener(this::createPressed);
257            create.setToolTipText(Bundle.getMessage(buttonHintKey));  // NOI18N
258        }
259        addLogixNGFrame.pack();
260        addLogixNGFrame.setVisible(true);
261        _autoSystemName.setSelected(false);
262        InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
263            _autoSystemName.setSelected(prefMgr.getCheckboxPreferenceState(systemNameAuto, true));
264        });
265    }
266
267    protected abstract JPanel makeAddFrame(String titleId, String startMessageId);
268
269    /**
270     * Enable/disable fields for data entry when user selects to have system
271     * name automatically generated.
272     */
273    void autoSystemName() {
274        if (_autoSystemName.isSelected()) {
275            _systemName.setEnabled(false);
276            _sysNameLabel.setEnabled(false);
277        } else {
278            _systemName.setEnabled(true);
279            _sysNameLabel.setEnabled(true);
280        }
281    }
282
283    /**
284     * Respond to the Cancel button in Add bean window.
285     * <p>
286     * Note: Also get there if the user closes the Add bean window.
287     *
288     * @param e The event heard
289     */
290    void cancelAddPressed(ActionEvent e) {
291        addLogixNGFrame.setVisible(false);
292        addLogixNGFrame.dispose();
293        addLogixNGFrame = null;
294        _inCopyMode = false;
295        if (f != null) {
296            f.setVisible(true);
297        }
298    }
299
300    /**
301     * Respond to the Copy bean button in Add bean window.
302     * <p>
303     * Provides a pane to set new properties of the copy.
304     *
305     * @param sName system name of bean to be copied
306     */
307    void copyPressed(String sName) {
308        if (!checkFlags(sName)) {
309            return;
310        }
311
312        Runnable t = new Runnable() {
313            @Override
314            public void run() {
315//                JmriJOptionPane.showMessageDialog(null, "Copy is not implemented yet.", "Error", JmriJOptionPane.ERROR_MESSAGE);
316
317                JPanel panel5 = makeAddFrame("TitleCopyLogixNG", "Copy");    // NOI18N
318                // Create bean
319                JButton create = new JButton(Bundle.getMessage("ButtonCopy"));  // NOI18N
320                panel5.add(create);
321                create.addActionListener((ActionEvent e) -> {
322                    copyBeanPressed(e);
323                });
324                addLogixNGFrame.pack();
325                addLogixNGFrame.setVisible(true);
326                _autoSystemName.setSelected(false);
327                InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
328                    _autoSystemName.setSelected(prefMgr.getCheckboxPreferenceState(systemNameAuto, true));
329                });
330
331                _inCopyMode = false;
332            }
333        };
334        log.debug("copyPressed started for {}", sName);  // NOI18N
335        javax.swing.SwingUtilities.invokeLater(t);
336        _inCopyMode = true;
337        _logixNGSysName = sName;
338    }
339
340    String _logixNGSysName;
341
342    protected void copyBean(@Nonnull E sourceBean, @Nonnull E targetBean) {
343        throw new UnsupportedOperationException("Not implemented");
344    }
345
346    protected boolean isCopyBeanSupported() {
347        return false;
348    }
349
350    protected boolean isExecuteSupported() {
351        return false;
352    }
353
354    protected void execute(@Nonnull E bean) {
355        throw new UnsupportedOperationException("Not implemented");
356    }
357
358    /**
359     * Copy the bean as configured in the Copy set up pane.
360     *
361     * @param e the event heard
362     */
363    private void copyBeanPressed(ActionEvent e) {
364
365        String uName = _addUserName.getText().trim();
366        if (uName.length() == 0) {
367            uName = null;
368        }
369        E targetBean;
370        if (_autoSystemName.isSelected()) {
371            if (!checkLogixNGUserName(uName)) {
372                return;
373            }
374            targetBean = createBean(uName);
375        } else {
376            if (!checkLogixNGSysName()) {
377                return;
378            }
379            String sName = _systemName.getText().trim();
380            // check if a bean with this name already exists
381            boolean createLogix = true;
382            targetBean = getManager().getBySystemName(sName);
383            if (targetBean != null) {
384                int result = JmriJOptionPane.showConfirmDialog(f,
385                        Bundle.getMessage("ConfirmLogixDuplicate", sName, _logixNGSysName), // NOI18N
386                        Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION,    // NOI18N
387                        JmriJOptionPane.QUESTION_MESSAGE);
388                if (JmriJOptionPane.NO_OPTION == result) {
389                    return;
390                }
391                createLogix = false;
392                String userName = targetBean.getUserName();
393                if (userName != null && userName.length() > 0) {
394                    _addUserName.setText(userName);
395                    uName = userName;
396                }
397            } else if (!checkLogixNGUserName(uName)) {
398                return;
399            }
400            if (createLogix) {
401                // Create the new LogixNG
402                targetBean = createBean(sName, uName);
403                if (targetBean == null) {
404                    // should never get here unless there is an assignment conflict
405                    log.error("Failure to create LogixNG with System Name: {}", sName);  // NOI18N
406                    return;
407                }
408            } else if (targetBean == null) {
409                log.error("Error targetLogix is null!");  // NOI18N
410                return;
411            } else {
412                targetBean.setUserName(uName);
413            }
414        }
415        E sourceBean = getManager().getBySystemName(_logixNGSysName);
416        if (sourceBean != null) copyBean(sourceBean, targetBean);
417        else log.error("Error targetLogix is null!");  // NOI18N
418        cancelAddPressed(null);
419    }
420
421    /**
422     * Check and warn if a string is already in use as the user name of a LogixNG.
423     *
424     * @param uName the suggested name
425     * @return true if not in use
426     */
427    boolean checkLogixNGUserName(String uName) {
428        // check if a bean with the same user name exists
429        if (uName != null && uName.trim().length() > 0) {
430            E x = getManager().getByUserName(uName);
431            if (x != null) {
432                // A bean with this user name already exists
433                JmriJOptionPane.showMessageDialog(addLogixNGFrame,
434                        Bundle.getMessage("LogixNGError3"), Bundle.getMessage("ErrorTitle"), // NOI18N
435                        JmriJOptionPane.ERROR_MESSAGE);
436                return false;
437            }
438        }
439        return true;
440    }
441
442    /**
443     * Check validity of bean system name.
444     * <p>
445     * Fixes name if it doesn't start with "IQ".
446     *
447     * @return false if name has length &lt; 1 after displaying a dialog
448     */
449    boolean checkLogixNGSysName() {
450        String sName = _systemName.getText();
451        if ((sName.length() < 1)) {
452            // Entered system name is blank or too short
453            JmriJOptionPane.showMessageDialog(addLogixNGFrame,
454                    Bundle.getMessage("LogixNGError8"), Bundle.getMessage("ErrorTitle"), // NOI18N
455                    JmriJOptionPane.ERROR_MESSAGE);
456            return false;
457        }
458        if ((sName.length() < 2) || (sName.charAt(0) != 'I')
459                || (sName.charAt(1) != 'Q')) {
460            // System name does not begin with IQ:, prefix IQ: to it
461            String s = sName;
462            sName = "IQ" + s;  // NOI18N
463        }
464        _systemName.setText(sName);
465        return true;
466    }
467
468    /**
469     * Check if another bean editing session is currently open or no system
470     * name is provided.
471     *
472     * @param sName system name of bean to be copied
473     * @return true if a new session may be started
474     */
475    boolean checkFlags(String sName) {
476        if (_inEditMode) {
477            // Already editing a bean, ask for completion of that edit
478            JmriJOptionPane.showMessageDialog(null,
479                    Bundle.getMessage("LogixNGError32", _curNamedBean.getSystemName()),
480                    Bundle.getMessage("ErrorTitle"),
481                    JmriJOptionPane.ERROR_MESSAGE);
482            if (_editor != null) {
483                _editor.bringToFront();
484            }
485            return false;
486        }
487
488        if (_inCopyMode) {
489            // Already editing a bean, ask for completion of that edit
490            JmriJOptionPane.showMessageDialog(null,
491                    Bundle.getMessage("LogixNGError31", _logixNGSysName),
492                    Bundle.getMessage("ErrorTitle"), // NOI18N
493                    JmriJOptionPane.ERROR_MESSAGE);
494            return false;
495        }
496
497        if (sName != null) {
498            // check if a bean with this name exists
499            E x = getManager().getBySystemName(sName);
500            if (x == null) {
501                // bean does not exist, so cannot be edited
502                log.error("No bean with system name: {}", sName);
503                JmriJOptionPane.showMessageDialog(null,
504                        Bundle.getMessage("LogixNGError5"),
505                        Bundle.getMessage("ErrorTitle"), // NOI18N
506                        JmriJOptionPane.ERROR_MESSAGE);
507                return false;
508            }
509        }
510        return true;
511    }
512
513    /**
514     * Respond to the Create bean button in Add bean window.
515     *
516     * @param e The event heard
517     */
518    void createPressed(ActionEvent e) {
519        // possible change
520        _showReminder = true;
521        String sName;
522        String uName = _addUserName.getText().trim();
523        if (uName.length() == 0) {
524            uName = null;
525        }
526        if (_autoSystemName.isSelected()) {
527            if (!checkLogixNGUserName(uName)) {
528                return;
529            }
530            try {
531                _curNamedBean = createBean(uName);
532            } catch (BadSystemNameException | BadUserNameException ex) {
533                JmriJOptionPane.showMessageDialog(addLogixNGFrame, ex.getLocalizedMessage(),
534                        Bundle.getMessage("ErrorTitle"), // NOI18N
535                        JmriJOptionPane.ERROR_MESSAGE);
536                return;
537            }
538            if (_curNamedBean == null) {
539                log.error("Failure to create bean with System Name: {}", "none");  // NOI18N
540                return;
541            }
542            sName = _curNamedBean.getSystemName();
543        } else {
544            if (!checkLogixNGSysName()) {
545                return;
546            }
547            // Get validated system name
548            sName = _systemName.getText();
549            // check if a bean with this name already exists
550            E x;
551            try {
552                x = getManager().getBySystemName(sName);
553            } catch (Exception ex) {
554                // user input no good
555                handleCreateException(sName);
556                return;  // without creating
557            }
558            if (x != null) {
559                // bean already exists
560                JmriJOptionPane.showMessageDialog(addLogixNGFrame, Bundle.getMessage("LogixNGError1"),
561                        Bundle.getMessage("ErrorTitle"), // NOI18N
562                        JmriJOptionPane.ERROR_MESSAGE);
563                return;
564            }
565            if (!checkLogixNGUserName(uName)) {
566                return;
567            }
568            // Create the new bean
569            _curNamedBean = createBean(sName, uName);
570            if (_curNamedBean == null) {
571                // should never get here unless there is an assignment conflict
572                log.error("Failure to create bean with System Name: {}", sName);  // NOI18N
573                return;
574            }
575        }
576        cancelAddPressed(null);
577        // create the Edit bean Window
578        editPressed(sName);
579        InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
580            prefMgr.setCheckboxPreferenceState(systemNameAuto, _autoSystemName.isSelected());
581        });
582    }
583
584    void handleCreateException(String sysName) {
585        JmriJOptionPane.showMessageDialog(addLogixNGFrame,
586                Bundle.getMessage("ErrorLogixAddFailed", sysName), // NOI18N
587                Bundle.getMessage("ErrorTitle"), // NOI18N
588                JmriJOptionPane.ERROR_MESSAGE);
589    }
590
591    // ------------ Methods for Edit bean Pane ------------
592
593    /**
594     * Respond to the Edit button pressed in LogixNG table.
595     *
596     * @param sName system name of LogixNG to be edited
597     */
598    void editPressed(String sName) {
599        _curNamedBean = getManager().getBySystemName(sName);
600        if (!checkFlags(sName)) {
601            return;
602        }
603
604        // Create a new bean edit view, add the listener.
605        _editor = getEditor(m, sName);
606
607        if (_editor == null) return;    // Editor not implemented yet for LogixNG Tables
608
609        _inEditMode = true;
610
611        _editor.addEditorEventListener((data) -> {
612            String lgxName = sName;
613            data.forEach((key, value) -> {
614                if (key.equals("Finish")) {                  // NOI18N
615                    _editor = null;
616                    _inEditMode = false;
617                    f.setVisible(true);
618                } else if (key.equals("Delete")) {           // NOI18N
619                    _inEditMode = false;
620                    deletePressed(value);
621                } else if (key.equals("chgUname")) {         // NOI18N
622                    E x = getManager().getBySystemName(lgxName);
623                    if (x == null) {
624                        log.error("Found no logixNG for name {} when changing user name (2)", lgxName);
625                        return;
626                    }
627                    x.setUserName(value);
628                    m.fireTableDataChanged();
629                }
630            });
631        });
632    }
633
634    /**
635     * Display reminder to save.
636     */
637    void showSaveReminder() {
638        if (_showReminder && !_checkEnabled) {
639            if (InstanceManager.getNullableDefault(jmri.UserPreferencesManager.class) != null) {
640                InstanceManager.getDefault(jmri.UserPreferencesManager.class).
641                        showInfoMessage(Bundle.getMessage("ReminderTitle"), Bundle.getMessage("ReminderSaveString", Bundle.getMessage("MenuItemLogixNGTable")), // NOI18N
642                                getClassName(),
643                                "remindSaveLogix");  // NOI18N
644            }
645        }
646    }
647
648    @Override
649    public void setMessagePreferencesDetails() {
650        HashMap<Integer, String> options = new HashMap<>(3);
651        options.put(0x00, Bundle.getMessage("DeleteAsk"));      // NOI18N
652        options.put(0x01, Bundle.getMessage("DeleteNever"));    // NOI18N
653        options.put(0x02, Bundle.getMessage("DeleteAlways"));   // NOI18N
654        jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class).setMessageItemDetails(getClassName(), "delete", Bundle.getMessage("DeleteLogixNG"), options, 0x00);  // NOI18N
655        jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails(getClassName(), "remindSaveLogixNG", Bundle.getMessage("HideSaveReminder"));  // NOI18N
656        super.setMessagePreferencesDetails();
657    }
658
659    /**
660     * Respond to the Delete combo selection bean window delete request.
661     *
662     * @param sName system name of bean to be deleted
663     */
664    void deletePressed(String sName) {
665        if (!checkFlags(sName)) {
666            return;
667        }
668
669        final E x = getManager().getBySystemName(sName);
670
671        if (x == null) return;  // This should never happen
672
673        deleteBean.delete(x, hasChildren(x), (t)->{deleteBean(t);},
674                (t,list)->{getListenerRefsIncludingChildren(t,list);},
675                getClassName());
676    }
677
678    /**
679     * Respond to the Execute combo selection bean window execute request.
680     *
681     * @param sName system name of bean to be deleted
682     */
683    void executePressed(String sName) {
684        final E x = getManager().getBySystemName(sName);
685
686        if (x == null) return;  // This should never happen
687
688        execute(x);
689    }
690
691    @Override
692    public String getClassDescription() {
693        return Bundle.getMessage("TitleLogixNGTable");        // NOI18N
694    }
695
696    @Override
697    protected String getClassName() {
698        // The class that is returned must have a default constructor,
699        // a constructor with no parameters.
700        return jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName();
701    }
702
703// ------------ Methods for Conditional Browser Window ------------
704    /**
705     * Respond to the Browse button pressed in bean table.
706     *
707     * @param sName The selected bean system name
708     */
709    void browserPressed(String sName) {
710        // bean was found, create the window
711        _curNamedBean = getManager().getBySystemName(sName);
712        getPrintTreeSettings();
713        makeBrowserWindow();
714    }
715
716    void getPrintTreeSettings() {
717        // Set options
718        InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
719            _printTreeSettings._printLineNumbers = prefMgr.getSimplePreferenceState(PRINT_LINE_NUMBERS_OPTION);
720            _printTreeSettings._printErrorHandling = prefMgr.getSimplePreferenceState(PRINT_ERROR_HANDLING_OPTION);
721            _printTreeSettings._printNotConnectedSockets = prefMgr.getSimplePreferenceState(PRINT_NOT_CONNECTED_OPTION);
722            _printTreeSettings._printLocalVariables = prefMgr.getSimplePreferenceState(PRINT_LOCAL_VARIABLES_OPTION);
723            _printTreeSettings._printSystemNames = prefMgr.getSimplePreferenceState(PRINT_SYSTEM_NAMES_OPTION);
724        });
725    }
726
727    /**
728     * Update text in the browser window.
729     */
730    void updateBrowserText() {
731        if (_textContent != null) {
732            _textContent.setText(getBeanText(_curNamedBean));
733        }
734    }
735
736    /**
737     * Create and initialize the conditionalNGs browser window.
738     */
739    void makeBrowserWindow() {
740        JmriJFrame condBrowserFrame = new JmriJFrame(this.getBrowserTitle(), false, true);
741
742        condBrowserFrame.addWindowListener(new WindowAdapter() {
743            @Override
744            public void windowClosed(WindowEvent e) {
745                _textContent = null;
746            }
747        });
748
749        Container contentPane = condBrowserFrame.getContentPane();
750        contentPane.setLayout(new BorderLayout());
751
752        // bean header information
753        JPanel topPanel = new JPanel();
754        String tStr = Bundle.getMessage("BrowserLogixNG") + " " + _curNamedBean.getSystemName() + "    " // NOI18N
755                + _curNamedBean.getUserName() + "    "
756                + (isEnabled(_curNamedBean)
757                        ? Bundle.getMessage("BrowserEnabled") // NOI18N
758                        : Bundle.getMessage("BrowserDisabled"));  // NOI18N
759        topPanel.add(new JLabel(tStr));
760        contentPane.add(topPanel, BorderLayout.NORTH);
761
762        // Build the conditionalNGs listing
763        _textContent = new JTextArea(this.getBeanText(_curNamedBean));
764        if (browseMonoSpace()) {
765            _textContent.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
766        }
767        JScrollPane scrollPane = new JScrollPane(_textContent);
768        contentPane.add(scrollPane);
769
770        JPanel bottomPanel = new JPanel();
771        bottomPanel.setLayout(new BorderLayout());
772        JButton helpBrowse = new JButton(Bundle.getMessage("MenuHelp"));   // NOI18N
773        bottomPanel.add(helpBrowse, BorderLayout.WEST);
774        helpBrowse.addActionListener((ActionEvent e) -> {
775            JmriJOptionPane.showMessageDialog(condBrowserFrame,
776                    Bundle.getMessage("LogixNG_Browse_HelpText"),   // NOI18N
777                    Bundle.getMessage("BrowserHelpTitle"),  // NOI18N
778                    JmriJOptionPane.INFORMATION_MESSAGE);
779        });
780
781        JPanel settingsPanel = getSettingsPanel();
782        bottomPanel.add(settingsPanel, BorderLayout.CENTER);
783
784        JButton saveBrowse = new JButton(Bundle.getMessage("BrowserSaveButton"));   // NOI18N
785        saveBrowse.setToolTipText(Bundle.getMessage("BrowserSaveButtonHint"));      // NOI18N
786        bottomPanel.add(saveBrowse, BorderLayout.EAST);
787        saveBrowse.addActionListener((ActionEvent e) -> {
788            saveBrowserPressed();
789        });
790        contentPane.add(bottomPanel, BorderLayout.SOUTH);
791
792        condBrowserFrame.pack();
793        condBrowserFrame.setVisible(true);
794    }  // makeBrowserWindow
795
796    JFileChooser userFileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
797
798    /**
799     * Save the bean browser window content to a text file.
800     */
801    void saveBrowserPressed() {
802        userFileChooser.setApproveButtonText(Bundle.getMessage("BrowserSaveDialogApprove"));  // NOI18N
803        userFileChooser.setDialogTitle(Bundle.getMessage("BrowserSaveDialogTitle"));  // NOI18N
804        userFileChooser.rescanCurrentDirectory();
805        // Default to logixNG system name.txt
806        String suggestedFileName = _curNamedBean.getSystemName().replace(':', '_') + ".txt";
807        userFileChooser.setSelectedFile(new File(suggestedFileName));  // NOI18N
808        int retVal = userFileChooser.showSaveDialog(null);
809        if (retVal != JFileChooser.APPROVE_OPTION) {
810            log.debug("Save browser content stopped, no file selected");  // NOI18N
811            return;  // give up if no file selected or cancel pressed
812        }
813        File file = userFileChooser.getSelectedFile();
814        log.debug("Save browser content to '{}'", file);  // NOI18N
815
816        if (file.exists()) {
817            Object[] options = {Bundle.getMessage("BrowserSaveDuplicateReplace"),  // NOI18N
818                    Bundle.getMessage("BrowserSaveDuplicateAppend"),  // NOI18N
819                    Bundle.getMessage("ButtonCancel")};               // NOI18N
820            int selectedOption = JmriJOptionPane.showOptionDialog(null,
821                    Bundle.getMessage("BrowserSaveDuplicatePrompt", file.getName()), // NOI18N
822                    Bundle.getMessage("BrowserSaveDuplicateTitle"),   // NOI18N
823                    JmriJOptionPane.DEFAULT_OPTION,
824                    JmriJOptionPane.WARNING_MESSAGE,
825                    null, options, options[0]);
826            if (selectedOption == 2 || selectedOption == -1) {
827                log.debug("Save browser content stopped, file replace/append cancelled");  // NOI18N
828                return;  // Cancel selected or dialog box closed
829            }
830            if (selectedOption == 0) {
831                FileUtil.delete(file);  // Replace selected
832            }
833        }
834
835        // Create the file content
836        String tStr = Bundle.getMessage("BrowserLogixNG") + " " + _curNamedBean.getSystemName() + "    "  // NOI18N
837                + _curNamedBean.getUserName() + "    "
838                + (isEnabled(_curNamedBean)
839                        ? Bundle.getMessage("BrowserEnabled")    // NOI18N
840                        : Bundle.getMessage("BrowserDisabled"));  // NOI18N
841        try {
842            // Add bean Header inforation first
843            FileUtil.appendTextToFile(file, tStr);
844            FileUtil.appendTextToFile(file, "-".repeat(tStr.length()));
845            FileUtil.appendTextToFile(file, "");
846            FileUtil.appendTextToFile(file, _textContent.getText());
847        } catch (IOException e) {
848            log.error("Unable to write browser content to '{}'", file, e);  // NOI18N
849        }
850    }
851
852    protected JPanel getSettingsPanel() {
853        JPanel checkBoxPanel = new JPanel();
854
855        JCheckBox printLineNumbers = new JCheckBox(Bundle.getMessage("LogixNG_Browse_PrintLineNumbers"));
856        printLineNumbers.setSelected(_printTreeSettings._printLineNumbers);
857        printLineNumbers.addChangeListener((event) -> {
858            if (_printTreeSettings._printLineNumbers != printLineNumbers.isSelected()) {
859                _printTreeSettings._printLineNumbers = printLineNumbers.isSelected();
860                InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
861                    prefMgr.setSimplePreferenceState(PRINT_LINE_NUMBERS_OPTION, printLineNumbers.isSelected());
862                });
863                updateBrowserText();
864            }
865        });
866
867        JCheckBox printErrorHandling = new JCheckBox(Bundle.getMessage("LogixNG_Browse_PrintErrorHandling"));
868        printErrorHandling.setSelected(_printTreeSettings._printErrorHandling);
869        printErrorHandling.addChangeListener((event) -> {
870            if (_printTreeSettings._printErrorHandling != printErrorHandling.isSelected()) {
871                _printTreeSettings._printErrorHandling = printErrorHandling.isSelected();
872                InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
873                    prefMgr.setSimplePreferenceState(PRINT_ERROR_HANDLING_OPTION, printErrorHandling.isSelected());
874                });
875                updateBrowserText();
876            }
877        });
878
879        JCheckBox printNotConnectedSockets = new JCheckBox(Bundle.getMessage("LogixNG_Browse_PrintNotConnectedSocket"));
880        printNotConnectedSockets.setSelected(_printTreeSettings._printNotConnectedSockets);
881        printNotConnectedSockets.addChangeListener((event) -> {
882            if (_printTreeSettings._printNotConnectedSockets != printNotConnectedSockets.isSelected()) {
883                _printTreeSettings._printNotConnectedSockets = printNotConnectedSockets.isSelected();
884                updateBrowserText();
885                InstanceManager.getOptionalDefault(jmri.UserPreferencesManager.class).ifPresent((prefMgr) -> {
886                    prefMgr.setSimplePreferenceState(PRINT_NOT_CONNECTED_OPTION, printNotConnectedSockets.isSelected());
887                });
888            }
889        });
890
891        JCheckBox printLocalVariables = new JCheckBox(Bundle.getMessage("LogixNG_Browse_PrintLocalVariables"));
892        printLocalVariables.setSelected(_printTreeSettings._printLocalVariables);
893        printLocalVariables.addChangeListener((event) -> {
894            if (_printTreeSettings._printLocalVariables != printLocalVariables.isSelected()) {
895                _printTreeSettings._printLocalVariables = printLocalVariables.isSelected();
896                updateBrowserText();
897                InstanceManager.getOptionalDefault(jmri.UserPreferencesManager.class).ifPresent((prefMgr) -> {
898                    prefMgr.setSimplePreferenceState(PRINT_LOCAL_VARIABLES_OPTION, printLocalVariables.isSelected());
899                });
900            }
901        });
902
903        JCheckBox printSystemNames = new JCheckBox(Bundle.getMessage("LogixNG_Browse_PrintSystemNames"));
904        printSystemNames.setSelected(_printTreeSettings._printSystemNames);
905        printSystemNames.addChangeListener((event) -> {
906            if (_printTreeSettings._printSystemNames != printSystemNames.isSelected()) {
907                _printTreeSettings._printSystemNames = printSystemNames.isSelected();
908                InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
909                    prefMgr.setSimplePreferenceState(PRINT_SYSTEM_NAMES_OPTION, printSystemNames.isSelected());
910                });
911                updateBrowserText();
912            }
913        });
914
915        if (this instanceof LogixNGTableAction || this instanceof LogixNGModuleTableAction) {
916            checkBoxPanel.add(printLineNumbers);
917            checkBoxPanel.add(printErrorHandling);
918            checkBoxPanel.add(printNotConnectedSockets);
919            checkBoxPanel.add(printLocalVariables);
920            checkBoxPanel.add(printSystemNames);
921        }
922
923        return checkBoxPanel;
924    }
925
926
927
928    protected class TableModel extends BeanTableDataModel<E> {
929
930        // overlay the state column with the edit column
931        static public final int ENABLECOL = VALUECOL;
932        static public final int EDITCOL = DELETECOL;
933        protected String enabledString = Bundle.getMessage("ColumnHeadEnabled");  // NOI18N
934
935        @Override
936        public String getColumnName(int col) {
937            if (col == EDITCOL) {
938                return Bundle.getMessage("ColumnHeadMenu");     // This makes it easier to test the table
939            }
940            if (col == ENABLECOL) {
941                return enabledString;
942            }
943            return super.getColumnName(col);
944        }
945
946        @Override
947        public Class<?> getColumnClass(int col) {
948            if (col == EDITCOL) {
949                return String.class;
950            }
951            if (col == ENABLECOL) {
952                return Boolean.class;
953            }
954            return super.getColumnClass(col);
955        }
956
957        @Override
958        public int getPreferredWidth(int col) {
959            // override default value for SystemName and UserName columns
960            if (col == SYSNAMECOL) {
961                return new JTextField(12).getPreferredSize().width;
962            }
963            if (col == USERNAMECOL) {
964                return new JTextField(17).getPreferredSize().width;
965            }
966            if (col == EDITCOL) {
967                return new JTextField(12).getPreferredSize().width;
968            }
969            if (col == ENABLECOL) {
970                return new JTextField(5).getPreferredSize().width;
971            }
972            return super.getPreferredWidth(col);
973        }
974
975        @Override
976        public boolean isCellEditable(int row, int col) {
977            if (col == EDITCOL) {
978                return true;
979            }
980            if (col == ENABLECOL) {
981                return true;
982            }
983            return super.isCellEditable(row, col);
984        }
985
986        @SuppressWarnings("unchecked")  // Unchecked cast from Object to E
987        @Override
988        public Object getValueAt(int row, int col) {
989            if (col == EDITCOL) {
990                return Bundle.getMessage("ButtonSelect");  // NOI18N
991            } else if (col == ENABLECOL) {
992                E x = (E) getValueAt(row, SYSNAMECOL);
993                if (x == null) {
994                    return null;
995                }
996                return isEnabled(x);
997            } else {
998                return super.getValueAt(row, col);
999            }
1000        }
1001
1002        @SuppressWarnings("unchecked")  // Unchecked cast from Object to E
1003        @Override
1004        public void setValueAt(Object value, int row, int col) {
1005            if (col == EDITCOL) {
1006                // set up to edit
1007                String sName = ((NamedBean) getValueAt(row, SYSNAMECOL)).getSystemName();
1008                if (Bundle.getMessage("ButtonEdit").equals(value)) {  // NOI18N
1009                    editPressed(sName);
1010
1011                } else if (Bundle.getMessage("BrowserButton").equals(value)) {  // NOI18N
1012                    conditionalRowNumber = row;
1013                    browserPressed(sName);
1014
1015                } else if (Bundle.getMessage("ButtonCopy").equals(value)) {  // NOI18N
1016                    copyPressed(sName);
1017
1018                } else if (Bundle.getMessage("ButtonDelete").equals(value)) {  // NOI18N
1019                    deletePressed(sName);
1020
1021                } else if (Bundle.getMessage("LogixNG_ButtonExecute").equals(value)) {  // NOI18N
1022                    executePressed(sName);
1023                }
1024            } else if (col == ENABLECOL) {
1025                // alternate
1026                E x = (E) getValueAt(row, SYSNAMECOL);
1027                boolean v = isEnabled(x);
1028                setEnabled(x, !v);
1029            } else {
1030                super.setValueAt(value, row, col);
1031            }
1032        }
1033
1034        /**
1035         * Delete the bean after all the checking has been done.
1036         * <p>
1037         * Deletes the NamedBean.
1038         *
1039         * @param bean of the NamedBean to delete
1040         */
1041        @Override
1042        protected void doDelete(E bean) {
1043            // delete the LogixNG
1044            AbstractLogixNGTableAction.this.deleteBean(bean);
1045        }
1046
1047        @Override
1048        protected boolean matchPropertyName(java.beans.PropertyChangeEvent e) {
1049            if (e.getPropertyName().equals(enabledString)) {
1050                return true;
1051            }
1052            return super.matchPropertyName(e);
1053        }
1054
1055        @Override
1056        public Manager<E> getManager() {
1057            return AbstractLogixNGTableAction.this.getManager();
1058        }
1059
1060        @Override
1061        public E getBySystemName(String name) {
1062            return AbstractLogixNGTableAction.this.getManager().getBySystemName(name);
1063        }
1064
1065        @Override
1066        public E getByUserName(String name) {
1067            return AbstractLogixNGTableAction.this.getManager().getByUserName(name);
1068        }
1069
1070        @Override
1071        protected String getMasterClassName() {
1072            return getClassName();
1073        }
1074
1075        @Override
1076        public void configureTable(JTable table) {
1077            table.setDefaultRenderer(Boolean.class, new EnablingCheckboxRenderer());
1078            table.setDefaultRenderer(JComboBox.class, new jmri.jmrit.symbolicprog.ValueRenderer());
1079            table.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor());
1080            if (!(getManager() instanceof jmri.jmrit.logixng.LogixNG_Manager)) {
1081                table.getColumnModel().getColumn(2).setMinWidth(0);
1082                table.getColumnModel().getColumn(2).setMaxWidth(0);
1083            }
1084            super.configureTable(table);
1085        }
1086
1087        /**
1088         * Replace delete button with comboBox to edit/delete/copy/select NamedBean.
1089         *
1090         * @param table name of the NamedBean JTable holding the column
1091         */
1092        @Override
1093        protected void configDeleteColumn(JTable table) {
1094            JComboBox<String> editCombo = new JComboBox<>();
1095            editCombo.addItem(Bundle.getMessage("ButtonSelect"));  // NOI18N
1096            if (isEditSupported()) editCombo.addItem(Bundle.getMessage("ButtonEdit"));  // NOI18N
1097            editCombo.addItem(Bundle.getMessage("BrowserButton"));  // NOI18N
1098            if (isCopyBeanSupported()) editCombo.addItem(Bundle.getMessage("ButtonCopy"));  // NOI18N
1099            editCombo.addItem(Bundle.getMessage("ButtonDelete"));  // NOI18N
1100            if (isExecuteSupported()) editCombo.addItem(Bundle.getMessage("LogixNG_ButtonExecute"));  // NOI18N
1101            TableColumn col = table.getColumnModel().getColumn(BeanTableDataModel.DELETECOL);
1102            col.setCellEditor(new DefaultCellEditor(editCombo));
1103        }
1104
1105        // Not needed - here for interface compatibility
1106        @Override
1107        public void clickOn(NamedBean t) {
1108        }
1109
1110        @Override
1111        public String getValue(String s) {
1112            return "";
1113        }
1114
1115        @Override
1116        protected String getBeanType() {
1117//                 return Bundle.getMessage("BeanNameLogix");  // NOI18N
1118            return rbx.getString("BeanNameLogixNG");  // NOI18N
1119        }
1120    }
1121
1122
1123    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractLogixNGTableAction.class);
1124
1125}