001package jmri.jmrit.logixng.tools.swing;
002
003import java.awt.*;
004import java.awt.event.*;
005import java.beans.PropertyVetoException;
006import java.text.MessageFormat;
007import java.util.List;
008import java.util.*;
009import java.util.concurrent.atomic.AtomicBoolean;
010
011import javax.annotation.Nonnull;
012import javax.swing.*;
013import javax.swing.event.TreeModelEvent;
014import javax.swing.event.TreeModelListener;
015import javax.swing.tree.*;
016
017import jmri.*;
018import jmri.jmrit.logixng.FemaleSocket;
019import jmri.jmrit.logixng.*;
020import jmri.jmrit.logixng.SymbolTable.InitialValueType;
021import jmri.jmrit.logixng.swing.SwingConfiguratorInterface;
022import jmri.jmrit.logixng.swing.SwingTools;
023import jmri.jmrit.logixng.util.LogixNG_Thread;
024import jmri.jmrit.logixng.util.parser.swing.FunctionsHelpDialog;
025import jmri.util.swing.JmriJOptionPane;
026import jmri.util.ThreadingUtil;
027
028import org.apache.commons.lang3.mutable.MutableObject;
029
030/**
031 * Base class for LogixNG editors
032 *
033 * @author Daniel Bergqvist 2020
034 */
035public class TreeEditor extends TreeViewer {
036
037    // Enums used to configure TreeEditor
038    public enum EnableClipboard { EnableClipboard, DisableClipboard }
039    public enum EnableRootRemoveCutCopy { EnableRootRemoveCutCopy, DisableRootRemoveCutCopy }
040    public enum EnableRootPopup { EnableRootPopup, DisableRootPopup }
041    public enum EnableExecuteEvaluate { EnableExecuteEvaluate, DisableExecuteEvaluate }
042
043
044    private static final String ACTION_COMMAND_RENAME_SOCKET = "rename_socket";
045    private static final String ACTION_COMMAND_REMOVE = "remove";
046    private static final String ACTION_COMMAND_EDIT = "edit";
047    private static final String ACTION_COMMAND_CUT = "cut";
048    private static final String ACTION_COMMAND_COPY = "copy";
049    private static final String ACTION_COMMAND_PASTE = "paste";
050    private static final String ACTION_COMMAND_PASTE_COPY = "pasteCopy";
051    private static final String ACTION_COMMAND_ENABLE = "enable";
052    private static final String ACTION_COMMAND_DISABLE = "disable";
053    private static final String ACTION_COMMAND_LOCK = "lock";
054    private static final String ACTION_COMMAND_UNLOCK = "unlock";
055    private static final String ACTION_COMMAND_LOCAL_VARIABLES = "local_variables";
056    private static final String ACTION_COMMAND_CHANGE_USERNAME = "change_username";
057    private static final String ACTION_COMMAND_EXECUTE_EVALUATE = "execute_evaluate";
058//    private static final String ACTION_COMMAND_EXPAND_TREE = "expandTree";
059
060    // There should only be one clipboard editor open at any time so this is static.
061    // This field must only be accessed on the GUI thread.
062    private static ClipboardEditor _clipboardEditor = null;
063
064    private final LogixNGPreferences _prefs = InstanceManager.getDefault(LogixNGPreferences.class);
065
066    private JDialog _renameSocketDialog = null;
067    private JDialog _addItemDialog = null;
068    private JDialog _editActionExpressionDialog = null;
069    private JDialog _editLocalVariablesDialog = null;
070    private JDialog _changeUsernameDialog = null;
071    private final JTextField _socketNameTextField = new JTextField(20);
072    private final JTextField _systemName = new JTextField(20);
073    private final JTextField _addUserName = new JTextField(20);
074    private final JTextField _usernameField = new JTextField(50);
075
076    protected boolean _showReminder = false;
077    private boolean _lockPopupMenu = false;
078
079    private final JLabel _renameSocketLabel = new JLabel(Bundle.getMessage("SocketName") + ":");  // NOI18N
080    private final JCheckBox _autoSystemName = new JCheckBox(Bundle.getMessage("LabelAutoSysName"));   // NOI18N
081    private final JLabel _sysNameLabel = new JLabel(Bundle.getMessage("SystemName") + ":");  // NOI18N
082    private final JLabel _userNameLabel = new JLabel(Bundle.getMessage("UserName") + ":");   // NOI18N
083    private final String _systemNameAuto = getClassName() + ".AutoSystemName";             // NOI18N
084    private JButton _create;
085    private JButton _edit;
086
087    private SwingConfiguratorInterface _addSwingConfiguratorInterface;
088    private SwingConfiguratorInterface _addSwingConfiguratorInterfaceMaleSocket;
089    private SwingConfiguratorInterface _editSwingConfiguratorInterface;
090    private final List<Map.Entry<SwingConfiguratorInterface, Base>> _swingConfiguratorInterfaceList = new ArrayList<>();
091
092    private LocalVariableTableModel _localVariableTableModel;
093
094    private final boolean _enableClipboard;
095    private final boolean _disableRootRemoveCutCopy;
096    private final boolean _disableRootPopup;
097    private final boolean _enableExecuteEvaluate;
098
099    /**
100     * Construct a TreeEditor.
101     *
102     * @param femaleRootSocket         the root of the tree
103     * @param enableClipboard          should clipboard be enabled on the menu?
104     * @param enableRootRemoveCutCopy  should the popup menu items remove,
105     *                                 cut and copy be enabled or disabled?
106     * @param enableRootPopup          should the popup menu be disabled for root?
107     * @param enableExecuteEvaluate    should the popup menu show execute/evaluate?
108     */
109    public TreeEditor(
110            @Nonnull FemaleSocket femaleRootSocket,
111            EnableClipboard enableClipboard,
112            EnableRootRemoveCutCopy enableRootRemoveCutCopy,
113            EnableRootPopup enableRootPopup,
114            EnableExecuteEvaluate enableExecuteEvaluate) {
115
116        super(femaleRootSocket);
117        _enableClipboard = enableClipboard == EnableClipboard.EnableClipboard;
118        _disableRootRemoveCutCopy = enableRootRemoveCutCopy == EnableRootRemoveCutCopy.DisableRootRemoveCutCopy;
119        _disableRootPopup = enableRootPopup == EnableRootPopup.DisableRootPopup;
120        _enableExecuteEvaluate = enableExecuteEvaluate == EnableExecuteEvaluate.EnableExecuteEvaluate;
121    }
122
123    @Override
124    final public void initComponents() {
125        super.initComponents();
126
127        // The menu is created in parent class TreeViewer
128        JMenuBar menuBar = getJMenuBar();
129
130        JMenu toolsMenu = new JMenu(Bundle.getMessage("MenuTools"));
131        if (_enableClipboard) {
132            JMenuItem openClipboardItem = new JMenuItem(Bundle.getMessage("MenuOpenClipboard"));
133            openClipboardItem.addActionListener((ActionEvent e) -> {
134                openClipboard();
135            });
136            toolsMenu.add(openClipboardItem);
137        }
138        menuBar.add(toolsMenu);
139
140        JTree tree = _treePane._tree;
141
142        tree.addKeyListener(new KeyListener(){
143            @Override
144            public void keyTyped(KeyEvent e) {
145            }
146
147            @Override
148            public void keyPressed(KeyEvent e) {
149                if (e.getModifiersEx() == Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()) {
150                    if (e.getKeyCode() == 'R') {    // Remove
151                        TreePath path = tree.getSelectionPath();
152                        if (path != null) {
153                            FemaleSocket femaleSocket = (FemaleSocket) path.getLastPathComponent();
154                            if (femaleSocket.isConnected()) {
155                                removeItem((FemaleSocket) path.getLastPathComponent(), path);
156                            }
157                        }
158                    }
159                    if (e.getKeyCode() == 'E') {    // Edit
160                        TreePath path = tree.getSelectionPath();
161                        if (path != null) {
162                            FemaleSocket femaleSocket = (FemaleSocket) path.getLastPathComponent();
163                            if (femaleSocket.isConnected()) {
164                                editItem(femaleSocket, path);
165                            }
166                        }
167                    }
168                    if (e.getKeyCode() == 'N') {    // New
169                        TreePath path = tree.getSelectionPath();
170                        if (path != null) {
171                            FemaleSocket femaleSocket = (FemaleSocket) path.getLastPathComponent();
172                            if (femaleSocket.isConnected()) {
173                                return;
174                            }
175                            if (parentIsSystem(femaleSocket) && abortEditAboutSystem(femaleSocket.getParent())) {
176                                return;
177                            }
178                            Rectangle rect = tree.getPathBounds(path);
179                            openPopupMenu(tree, path, rect.x, rect.y, true);
180                        }
181                    }
182                    if (e.getKeyCode() == 'D') {    // Disable
183                        TreePath path = tree.getSelectionPath();
184                        if (path != null) {
185                            FemaleSocket femaleSocket = (FemaleSocket) path.getLastPathComponent();
186                            if (femaleSocket.isConnected()) {
187                                doIt(ACTION_COMMAND_DISABLE, femaleSocket, path);
188                            }
189                        }
190                    }
191                }
192                if (e.getModifiersEx() == Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx() + InputEvent.SHIFT_DOWN_MASK) {
193                    if (e.getKeyCode() == 'V') {    // Paste copy
194                        TreePath path = tree.getSelectionPath();
195                        if (path != null) {
196                            FemaleSocket femaleSocket = (FemaleSocket) path.getLastPathComponent();
197                            if (!femaleSocket.isConnected()) {
198                                pasteCopy((FemaleSocket) path.getLastPathComponent(), path);
199                            }
200                        }
201                    }
202                    if (e.getKeyCode() == 'D') {    // Enable
203                        TreePath path = tree.getSelectionPath();
204                        if (path != null) {
205                            FemaleSocket femaleSocket = (FemaleSocket) path.getLastPathComponent();
206                            if (femaleSocket.isConnected()) {
207                                doIt(ACTION_COMMAND_ENABLE, femaleSocket, path);
208                            }
209                        }
210                    }
211                }
212
213                for (FemaleSocketOperation oper : FemaleSocketOperation.values()) {
214                    if (e.getKeyCode() == oper.getKeyCode()
215                            && e.getModifiersEx() == oper.getModifiers()) {
216
217                        TreePath path = tree.getSelectionPath();
218                        if (path != null) {
219                            FemaleSocket femaleSocket = (FemaleSocket) path.getLastPathComponent();
220                            if (femaleSocket.isSocketOperationAllowed(oper) && !parentIsLocked(femaleSocket)) {
221                                doIt(oper.name(), femaleSocket, path);
222                            }
223                        }
224                    }
225                }
226            }
227
228            @Override
229            public void keyReleased(KeyEvent e) {
230            }
231        });
232
233        var mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
234        tree.getActionMap().put(tree.getInputMap().get(KeyStroke.getKeyStroke(KeyEvent.VK_X, mask)), new AbstractAction() {
235            @Override
236            public void actionPerformed(ActionEvent e) {
237                TreePath path = tree.getSelectionPath();
238                if (path != null) {
239                    cutItem((FemaleSocket) path.getLastPathComponent(), path);
240                }
241            }
242        });
243
244        tree.getActionMap().put(tree.getInputMap().get(KeyStroke.getKeyStroke(KeyEvent.VK_C, mask)), new AbstractAction() {
245            @Override
246            public void actionPerformed(ActionEvent e) {
247                TreePath path = tree.getSelectionPath();
248                if (path != null) {
249                    copyItem((FemaleSocket) path.getLastPathComponent());
250                }
251            }
252        });
253
254        tree.getActionMap().put(tree.getInputMap().get(KeyStroke.getKeyStroke(KeyEvent.VK_V, mask)), new AbstractAction() {
255            @Override
256            public void actionPerformed(ActionEvent e) {
257                TreePath path = tree.getSelectionPath();
258                if (path != null) {
259                    pasteItem((FemaleSocket) path.getLastPathComponent(), path);
260                }
261            }
262        });
263
264
265        tree.addMouseListener(
266                new MouseAdapter() {
267                    // On Windows, the popup is opened on mousePressed,
268                    // on some other OS, the popup is opened on mouseReleased
269
270                    @Override
271                    public void mousePressed(MouseEvent e) {
272                        if (e.isPopupTrigger()) {
273                            openPopupMenu(tree, tree.getClosestPathForLocation(e.getX(), e.getY()), e.getX(), e.getY(), false);
274                        }
275                    }
276
277                    @Override
278                    public void mouseReleased(MouseEvent e) {
279                        if (e.isPopupTrigger()) {
280                            openPopupMenu(tree, tree.getClosestPathForLocation(e.getX(), e.getY()), e.getX(), e.getY(), false);
281                        }
282                    }
283                }
284        );
285    }
286
287    private void openPopupMenu(JTree tree, TreePath path, int x, int y, boolean onlyAddItems) {
288        if (isPopupMenuLocked()) return;
289
290        if (path != null) {
291            // Check that the user has clicked on a row.
292            Rectangle rect = tree.getPathBounds(path);
293            if ((y >= rect.y) && (y <= rect.y + rect.height)) {
294                // Select the row the user clicked on
295                tree.setSelectionPath(path);
296
297                FemaleSocket femaleSocket = (FemaleSocket) path.getLastPathComponent();
298                new PopupMenu(x, y, femaleSocket, path, onlyAddItems);
299            }
300        }
301    }
302
303    public static void openClipboard() {
304        if (_clipboardEditor == null) {
305            _clipboardEditor = new ClipboardEditor();
306            _clipboardEditor.initComponents();
307            _clipboardEditor.setVisible(true);
308
309            _clipboardEditor.addClipboardEventListener(() -> {
310                _clipboardEditor.clipboardData.forEach((key, value) -> {
311                    if (key.equals("Finish")) {                  // NOI18N
312                        _clipboardEditor = null;
313                    }
314                });
315            });
316        } else {
317            _clipboardEditor.setVisible(true);
318        }
319    }
320
321    private static String getClassName() {
322        return jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName();
323    }
324
325    /**
326     * Run the thread action on either the ConditionalNG thread or the
327     * GUI thread.
328     * If the conditionalNG is not null, run it on the conditionalNG thread.
329     * If the conditionalNG is null, run it on the GUI thread.
330     * The conditionalNG is null when editing the clipboard or a module.
331     * @param conditionalNG the conditionalNG or null if no conditionalNG
332     * @param ta the thread action
333     */
334    private void runOnConditionalNGThreadOrGUIThreadEventually(
335            ConditionalNG conditionalNG, ThreadingUtil.ThreadAction ta) {
336
337        if (conditionalNG != null) {
338            LogixNG_Thread thread = conditionalNG.getCurrentThread();
339            thread.runOnLogixNGEventually(ta);
340        } else {
341            // Run the thread action on the GUI thread. And we already are on the GUI thread.
342            ta.run();
343        }
344    }
345
346    /**
347     * When a pop-up action is selected that opens a dialog, the popup menu is locked until the
348     * dialog is closed.
349     * @return true if the popup menu is locked.
350     */
351    final protected boolean isPopupMenuLocked() {
352        if (_lockPopupMenu) {
353            JmriJOptionPane.showMessageDialog(this,
354                    Bundle.getMessage("TreeEditor_PopupLockMessage"),
355                    Bundle.getMessage("TreeEditor_PopupLockTitle"),
356                    JmriJOptionPane.INFORMATION_MESSAGE);
357        }
358        return _lockPopupMenu;
359    }
360
361    final protected void setPopupMenuLock(boolean lock) {
362        _lockPopupMenu = lock;
363    }
364
365
366    /**
367     * Respond to the Add menu choice in the popup menu.
368     *
369     * @param femaleSocket the female socket
370     * @param path the path to the item the user has clicked on
371     */
372    final protected void renameSocketPressed(FemaleSocket femaleSocket, TreePath path) {
373        setPopupMenuLock(true);
374        _renameSocketDialog = new JDialog(
375                this,
376                Bundle.getMessage(
377                        "RenameSocketDialogTitle",
378                        femaleSocket.getLongDescription()),
379                false);
380//        _renameSocketDialog.addHelpMenu(
381//                "package.jmri.jmrit.logixng.tools.swing.ConditionalNGAddEdit", true);     // NOI18N
382        _renameSocketDialog.setLocation(50, 30);
383        Container contentPanel = _renameSocketDialog.getContentPane();
384        contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
385
386        JPanel p;
387        p = new JPanel();
388//        p.setLayout(new FlowLayout());
389        p.setLayout(new java.awt.GridBagLayout());
390        java.awt.GridBagConstraints c = new java.awt.GridBagConstraints();
391        c.gridwidth = 1;
392        c.gridheight = 1;
393        c.gridx = 0;
394        c.gridy = 0;
395        c.anchor = java.awt.GridBagConstraints.EAST;
396        p.add(_renameSocketLabel, c);
397        c.gridx = 1;
398        c.gridy = 0;
399        c.anchor = java.awt.GridBagConstraints.WEST;
400        c.weightx = 1.0;
401        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
402        p.add(_socketNameTextField, c);
403        _socketNameTextField.setText(femaleSocket.getName());
404
405        contentPanel.add(p);
406
407        // set up Create and Cancel buttons
408        JPanel panel5 = new JPanel();
409        panel5.setLayout(new FlowLayout());
410        // Cancel
411        JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));    // NOI18N
412        panel5.add(cancel);
413        cancel.addActionListener((ActionEvent e) -> {
414            cancelRenameSocketPressed(null);
415        });
416        cancel.setToolTipText(Bundle.getMessage("CancelRenameLogixNGButtonHint"));      // NOI18N
417
418        _renameSocketDialog.addWindowListener(new java.awt.event.WindowAdapter() {
419            @Override
420            public void windowClosing(java.awt.event.WindowEvent e) {
421                cancelRenameSocketPressed(null);
422            }
423        });
424
425        _create = new JButton(Bundle.getMessage("ButtonOK"));  // NOI18N
426        panel5.add(_create);
427        _create.addActionListener((ActionEvent e) -> {
428            if (femaleSocket.validateName(_socketNameTextField.getText())) {
429                femaleSocket.setName(_socketNameTextField.getText());
430                cancelRenameSocketPressed(null);
431                for (TreeModelListener l : _treePane.femaleSocketTreeModel.listeners) {
432                    TreeModelEvent tme = new TreeModelEvent(
433                            femaleSocket,
434                            path.getPath()
435                    );
436                    l.treeNodesChanged(tme);
437                }
438                _treePane._tree.updateUI();
439                setPopupMenuLock(false);
440            } else {
441                JmriJOptionPane.showMessageDialog(null,
442                        Bundle.getMessage("ValidateFemaleSocketMessage", _socketNameTextField.getText()),
443                        Bundle.getMessage("ValidateFemaleSocketTitle"),
444                        JmriJOptionPane.ERROR_MESSAGE);
445            }
446        });
447
448        contentPanel.add(panel5);
449
450//        _renameSocketDialog.setLocationRelativeTo(component);
451        _renameSocketDialog.setLocationRelativeTo(null);
452        _renameSocketDialog.pack();
453        _renameSocketDialog.setVisible(true);
454    }
455
456    /**
457     * Respond to the Add menu choice in the popup menu.
458     *
459     * @param femaleSocket the female socket
460     * @param swingConfiguratorInterface the swing configurator used to configure the new class
461     * @param path the path to the item the user has clicked on
462     */
463    final protected void createAddFrame(FemaleSocket femaleSocket, TreePath path,
464            SwingConfiguratorInterface swingConfiguratorInterface) {
465        // possible change
466        _showReminder = true;
467        // make an Add Item Frame
468        if (_addItemDialog == null) {
469            MutableObject<String> commentStr = new MutableObject<>();
470            _addSwingConfiguratorInterface = swingConfiguratorInterface;
471            // Create item
472            _create = new JButton(Bundle.getMessage("ButtonCreate"));  // NOI18N
473            _create.addActionListener((ActionEvent e) -> {
474                _treePane._femaleRootSocket.unregisterListeners();
475
476                runOnConditionalNGThreadOrGUIThreadEventually(
477                        _treePane._femaleRootSocket.getConditionalNG(),
478                        () -> {
479
480                    List<String> errorMessages = new ArrayList<>();
481
482                    boolean isValid = true;
483
484                    if (!_prefs.getShowSystemUserNames()
485                            || (_systemName.getText().isEmpty() && _autoSystemName.isSelected())) {
486                        _systemName.setText(_addSwingConfiguratorInterface.getAutoSystemName());
487                    }
488
489                    checkAndAdjustSystemName();
490
491                    if (_addSwingConfiguratorInterface.getManager()
492                            .validSystemNameFormat(_systemName.getText()) != Manager.NameValidity.VALID) {
493                        isValid = false;
494                        errorMessages.add(Bundle.getMessage("InvalidSystemName", _systemName.getText()));
495                    }
496
497                    isValid &= _addSwingConfiguratorInterface.validate(errorMessages);
498
499                    if (isValid) {
500                        MaleSocket socket;
501                        if (_addUserName.getText().isEmpty()) {
502                            socket = _addSwingConfiguratorInterface.createNewObject(_systemName.getText(), null);
503                        } else {
504                            socket = _addSwingConfiguratorInterface.createNewObject(_systemName.getText(), _addUserName.getText());
505                        }
506                        _addSwingConfiguratorInterfaceMaleSocket.updateObject(socket);
507    //                    for (Map.Entry<SwingConfiguratorInterface, Base> entry : _swingConfiguratorInterfaceList) {
508    //                        entry.getKey().updateObject(entry.getValue());
509    //                    }
510                        socket.setComment(commentStr.getValue());
511                        try {
512                            femaleSocket.connect(socket);
513                        } catch (SocketAlreadyConnectedException ex) {
514                            throw new RuntimeException(ex);
515                        }
516
517                        femaleSocket.forEntireTree((Base b) -> {
518                            b.addPropertyChangeListener(_treePane);
519                        });
520
521                        ThreadingUtil.runOnGUIEventually(() -> {
522                            _addSwingConfiguratorInterface.dispose();
523                            _addItemDialog.dispose();
524                            _addItemDialog = null;
525
526                            for (TreeModelListener l : _treePane.femaleSocketTreeModel.listeners) {
527                                TreeModelEvent tme = new TreeModelEvent(
528                                        femaleSocket,
529                                        path.getPath()
530                                );
531                                l.treeNodesChanged(tme);
532                            }
533                            _treePane._tree.expandPath(path);
534                            _treePane._tree.updateUI();
535
536                            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
537                                prefMgr.setCheckboxPreferenceState(_systemNameAuto, _autoSystemName.isSelected());
538                            });
539                        });
540                        setPopupMenuLock(false);
541                    } else {
542                        StringBuilder errorMsg = new StringBuilder();
543                        for (String s : errorMessages) {
544                            if (errorMsg.length() > 0) errorMsg.append("<br>");
545                            errorMsg.append(s);
546                        }
547                        JmriJOptionPane.showMessageDialog(null,
548                                Bundle.getMessage("ValidateErrorMessage", errorMsg),
549                                Bundle.getMessage("ValidateErrorTitle"),
550                                JmriJOptionPane.ERROR_MESSAGE);
551                    }
552                    ThreadingUtil.runOnGUIEventually(() -> {
553                        if (_treePane._femaleRootSocket.isActive()) {
554                            _treePane._femaleRootSocket.registerListeners();
555                        }
556                    });
557                });
558            });
559            _create.setToolTipText(Bundle.getMessage("CreateButtonHint"));  // NOI18N
560
561            if (_addSwingConfiguratorInterface != null) {
562                makeAddEditFrame(true, femaleSocket, _create, commentStr);
563            }
564        }
565    }
566
567    /**
568     * Check the system name format.  Add prefix and/or $ as neeeded.
569     */
570    void checkAndAdjustSystemName() {
571        if (_autoSystemName.isSelected()) {
572            return;
573        }
574
575        var sName = _systemName.getText().trim();
576        var prefix = _addSwingConfiguratorInterface.getManager().getSubSystemNamePrefix();
577
578        if (!sName.isEmpty() && !sName.startsWith(prefix)) {
579            var isNumber = sName.matches("^\\d+$");
580            var hasDollar = sName.startsWith("$");
581
582            var newName = new StringBuilder(prefix);
583            if (!isNumber && !hasDollar) {
584                newName.append("$");
585            }
586            newName.append(sName);
587            sName = newName.toString();
588        }
589
590        _systemName.setText(sName);
591        return;
592    }
593
594    /**
595     * Respond to the Edit menu choice in the popup menu.
596     *
597     * @param femaleSocket the female socket
598     * @param path the path to the item the user has clicked on
599     */
600    final protected void editPressed(FemaleSocket femaleSocket, TreePath path) {
601        setPopupMenuLock(true);
602
603        // possible change
604        _showReminder = true;
605        // make an Edit Frame
606        if (_editActionExpressionDialog == null) {
607            Base object = femaleSocket.getConnectedSocket().getObject();
608            MutableObject<String> commentStr = new MutableObject<>(object.getComment());
609
610            // Edit ConditionalNG
611            _edit = new JButton(Bundle.getMessage("ButtonOK"));  // NOI18N
612            _edit.addActionListener((ActionEvent e) -> {
613
614                runOnConditionalNGThreadOrGUIThreadEventually(
615                        _treePane._femaleRootSocket.getConditionalNG(),
616                        () -> {
617
618                    List<String> errorMessages = new ArrayList<>();
619
620                    boolean isValid = true;
621
622                    if (_editSwingConfiguratorInterface.getManager() != null) {
623                        if (_editSwingConfiguratorInterface.getManager()
624                                .validSystemNameFormat(_systemName.getText()) != Manager.NameValidity.VALID) {
625                            isValid = false;
626                            errorMessages.add(Bundle.getMessage("InvalidSystemName", _systemName.getText()));
627                        }
628                    } else {
629                        log.debug("_editSwingConfiguratorInterface.getManager() returns null");
630                    }
631
632                    isValid &= _editSwingConfiguratorInterface.validate(errorMessages);
633
634                    boolean canClose = true;
635                    for (Map.Entry<SwingConfiguratorInterface, Base> entry : _swingConfiguratorInterfaceList) {
636                        if (!entry.getKey().canClose()) {
637                            canClose = false;
638                            break;
639                        }
640                    }
641
642                    if (isValid && canClose) {
643                        ThreadingUtil.runOnGUIEventually(() -> {
644                            femaleSocket.unregisterListeners();
645
646//                            Base object = femaleSocket.getConnectedSocket().getObject();
647                            if (_addUserName.getText().isEmpty()) {
648                                ((NamedBean)object).setUserName(null);
649                            } else {
650                                ((NamedBean)object).setUserName(_addUserName.getText());
651                            }
652                            ((NamedBean)object).setComment(commentStr.getValue());
653                            for (Map.Entry<SwingConfiguratorInterface, Base> entry : _swingConfiguratorInterfaceList) {
654                                entry.getKey().updateObject(entry.getValue());
655                                entry.getKey().dispose();
656                            }
657                            for (TreeModelListener l : _treePane.femaleSocketTreeModel.listeners) {
658                                TreeModelEvent tme = new TreeModelEvent(
659                                        femaleSocket,
660                                        path.getPath()
661                                );
662                                l.treeNodesChanged(tme);
663                            }
664                            _editActionExpressionDialog.dispose();
665                            _editActionExpressionDialog = null;
666                            _treePane._tree.updateUI();
667
668//                            if (femaleSocket.isActive()) femaleSocket.registerListeners();
669                            if (_treePane._femaleRootSocket.isActive()) {
670                                _treePane._femaleRootSocket.registerListeners();
671                            }
672                        });
673                        setPopupMenuLock(false);
674                    } else if (!isValid) {
675                        StringBuilder errorMsg = new StringBuilder();
676                        for (String s : errorMessages) {
677                            if (errorMsg.length() > 0) errorMsg.append("<br>");
678                            errorMsg.append(s);
679                        }
680                        ThreadingUtil.runOnGUIEventually(() -> {
681                            JmriJOptionPane.showMessageDialog(null,
682                                    Bundle.getMessage("ValidateErrorMessage", errorMsg),
683                                    Bundle.getMessage("ValidateErrorTitle"),
684                                    JmriJOptionPane.ERROR_MESSAGE);
685                        });
686                    }
687                });
688            });
689            _edit.setToolTipText(Bundle.getMessage("EditButtonHint"));  // NOI18N
690
691            makeAddEditFrame(false, femaleSocket, _edit, commentStr);
692        }
693    }
694
695    /**
696     * Create or edit action/expression dialog.
697     *
698     * @param addOrEdit true if add, false if edit
699     * @param femaleSocket the female socket to which we want to add something
700     * @param button a button to add to the dialog
701     * @param commentStr the new comment
702     */
703    final protected void makeAddEditFrame(
704            boolean addOrEdit,
705            FemaleSocket femaleSocket,
706            JButton button,
707            MutableObject<String> commentStr) {
708
709        JDialog dialog  = new JDialog(
710                this,
711                Bundle.getMessage(
712                        addOrEdit ? "AddMaleSocketDialogTitle" : "EditMaleSocketDialogTitle",
713                        femaleSocket.getLongDescription()),
714                false);
715//        frame.addHelpMenu(
716//                "package.jmri.jmrit.logixng.tools.swing.ConditionalNGAddEdit", true);     // NOI18N
717        Container contentPanel = dialog.getContentPane();
718        contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
719
720        JPanel p;
721        p = new JPanel();
722//        p.setLayout(new FlowLayout());
723        p.setLayout(new java.awt.GridBagLayout());
724        java.awt.GridBagConstraints c = new java.awt.GridBagConstraints();
725        c.gridwidth = 1;
726        c.gridheight = 1;
727        if (_prefs.getShowSystemUserNames()) {
728            c.gridx = 0;
729            c.gridy = 0;
730            c.anchor = java.awt.GridBagConstraints.EAST;
731            p.add(_sysNameLabel, c);
732            c.gridy = 1;
733            p.add(_userNameLabel, c);
734            c.gridy = 2;
735            c.gridx = 1;
736            c.gridy = 0;
737            c.anchor = java.awt.GridBagConstraints.WEST;
738            c.weightx = 1.0;
739            c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
740            p.add(_systemName, c);
741            c.gridy = 1;
742            p.add(_addUserName, c);
743            if (!femaleSocket.isConnected()) {
744                c.gridx = 2;
745                c.gridy = 1;
746                c.anchor = java.awt.GridBagConstraints.WEST;
747                c.weightx = 1.0;
748                c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
749                c.gridy = 0;
750                p.add(_autoSystemName, c);
751            }
752
753            if (addOrEdit) {
754                _systemName.setToolTipText(Bundle.getMessage("SystemNameHint",
755                        _addSwingConfiguratorInterface.getExampleSystemName()));
756                _addUserName.setToolTipText(Bundle.getMessage("UserNameHint"));
757            }
758        } else {
759            c.gridx = 0;
760            c.gridy = 0;
761        }
762        contentPanel.add(p);
763
764        if (femaleSocket.isConnected()) {
765            _systemName.setText(femaleSocket.getConnectedSocket().getSystemName());
766            _systemName.setEnabled(false);
767            _addUserName.setText(femaleSocket.getConnectedSocket().getUserName());
768        } else {
769            _systemName.setText("");
770            _systemName.setEnabled(true);
771            _addUserName.setText("");
772        }
773
774        // set up message
775        JPanel panel3 = new JPanel();
776        panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS));
777
778        // set up create and cancel buttons
779        JPanel panel5 = new JPanel();
780        panel5.setLayout(new FlowLayout());
781
782        Base object = null;
783
784        // Get panel for the item
785        _swingConfiguratorInterfaceList.clear();
786        List<JPanel> panels = new ArrayList<>();
787        if (femaleSocket.isConnected()) {
788            object = femaleSocket.getConnectedSocket();
789            while (object instanceof MaleSocket) {
790                SwingConfiguratorInterface swi =
791                        SwingTools.getSwingConfiguratorForClass(object.getClass());
792                panels.add(swi.getConfigPanel(object, panel5));
793                _swingConfiguratorInterfaceList.add(new HashMap.SimpleEntry<>(swi, object));
794                object = ((MaleSocket)object).getObject();
795            }
796            if (object != null) {
797                _editSwingConfiguratorInterface =
798                        SwingTools.getSwingConfiguratorForClass(object.getClass());
799                _editSwingConfiguratorInterface.setJDialog(dialog);
800                panels.add(_editSwingConfiguratorInterface.getConfigPanel(object, panel5));
801                _swingConfiguratorInterfaceList.add(new HashMap.SimpleEntry<>(_editSwingConfiguratorInterface, object));
802
803                dialog.setTitle(Bundle.getMessage(
804                        addOrEdit ? "AddMaleSocketDialogTitleWithType" : "EditMaleSocketDialogTitleWithType",
805                        femaleSocket.getLongDescription(),
806                        _editSwingConfiguratorInterface.toString())
807                );
808            } else {
809                // 'object' should be an action or expression but is null
810                JPanel panel = new JPanel();
811                panel.add(new JLabel("Error: femaleSocket.getConnectedSocket().getObject().getObject()....getObject() doesn't return a non MaleSocket"));
812                panels.add(panel);
813                log.error("femaleSocket.getConnectedSocket().getObject().getObject()....getObject() doesn't return a non MaleSocket");
814            }
815        } else {
816            Class<? extends MaleSocket> maleSocketClass =
817                    _addSwingConfiguratorInterface.getManager().getMaleSocketClass();
818            _addSwingConfiguratorInterfaceMaleSocket =
819                    SwingTools.getSwingConfiguratorForClass(maleSocketClass);
820
821            _addSwingConfiguratorInterfaceMaleSocket.setJDialog(dialog);
822            panels.add(_addSwingConfiguratorInterfaceMaleSocket.getConfigPanel(panel5));
823
824            _addSwingConfiguratorInterface.setJDialog(dialog);
825            panels.add(_addSwingConfiguratorInterface.getConfigPanel(panel5));
826
827            dialog.setTitle(Bundle.getMessage(
828                    addOrEdit ? "AddMaleSocketDialogTitleWithType" : "EditMaleSocketDialogTitleWithType",
829                    femaleSocket.getLongDescription(),
830                    _addSwingConfiguratorInterface.toString())
831            );
832        }
833        JPanel panel34 = new JPanel();
834        panel34.setLayout(new BoxLayout(panel34, BoxLayout.Y_AXIS));
835        for (int i = panels.size()-1; i >= 0; i--) {
836            JPanel panel = panels.get(i);
837            if (panel.getComponentCount() > 0) {
838                panel34.add(Box.createVerticalStrut(30));
839                panel34.add(panel);
840            }
841        }
842        panel3.add(panel34);
843        contentPanel.add(panel3);
844
845        // Edit comment
846        JButton editComment = new JButton(Bundle.getMessage("ButtonEditComment"));    // NOI18N
847        panel5.add(editComment);
848        String comment = object != null ? object.getComment() : "";
849        editComment.addActionListener((ActionEvent e) -> {
850            commentStr.setValue(new EditCommentDialog().showDialog(comment));
851        });
852
853        // Function help
854        JButton showFunctionHelp = new JButton(Bundle.getMessage("ButtonFunctionHelp"));    // NOI18N
855        panel5.add(showFunctionHelp);
856        showFunctionHelp.addActionListener((ActionEvent e) -> {
857            InstanceManager.getDefault(FunctionsHelpDialog.class).showDialog();
858        });
859//        showFunctionHelp.setToolTipText("FunctionHelpButtonHint");      // NOI18N
860
861        // Cancel
862        JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));    // NOI18N
863        panel5.add(cancel);
864        cancel.addActionListener((ActionEvent e) -> {
865            if (!femaleSocket.isConnected()) {
866                cancelCreateItem(null);
867            } else {
868                cancelEditPressed(null);
869            }
870        });
871        cancel.setToolTipText(Bundle.getMessage("LogixNG_CancelButtonHint"));      // NOI18N
872
873        panel5.add(button);
874
875        dialog.addWindowListener(new java.awt.event.WindowAdapter() {
876            @Override
877            public void windowClosing(java.awt.event.WindowEvent e) {
878                if (addOrEdit) {
879                    cancelCreateItem(null);
880                } else {
881                    cancelEditPressed(null);
882                }
883            }
884        });
885
886        contentPanel.add(panel5);
887
888        _autoSystemName.addItemListener((ItemEvent e) -> {
889            autoSystemName();
890        });
891//        addLogixNGFrame.setLocationRelativeTo(component);
892        dialog.pack();
893        dialog.setLocationRelativeTo(null);
894
895        dialog.getRootPane().setDefaultButton(button);
896
897        if (addOrEdit) {
898            _addItemDialog = dialog;
899        } else {
900            _editActionExpressionDialog = dialog;
901        }
902
903        _autoSystemName.setSelected(true);
904        InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
905            _autoSystemName.setSelected(prefMgr.getCheckboxPreferenceState(_systemNameAuto, true));
906        });
907
908        _systemName.setEnabled(addOrEdit);
909
910        dialog.setVisible(true);
911    }
912
913    /**
914     * Respond to the Local Variables menu choice in the popup menu.
915     *
916     * @param femaleSocket the female socket
917     * @param path the path to the item the user has clicked on
918     */
919    final protected void editLocalVariables(FemaleSocket femaleSocket, TreePath path) {
920        // possible change
921        _showReminder = true;
922        setPopupMenuLock(true);
923        // make an Edit Frame
924        if (_editLocalVariablesDialog == null) {
925            MaleSocket maleSocket = femaleSocket.getConnectedSocket();
926
927            // Edit ConditionalNG
928            _edit = new JButton(Bundle.getMessage("ButtonOK"));  // NOI18N
929            _edit.addActionListener((ActionEvent e) -> {
930                List<String> errorMessages = new ArrayList<>();
931                boolean hasErrors = false;
932                for (SymbolTable.VariableData v : _localVariableTableModel.getVariables()) {
933                    if (v.getName().isEmpty()) {
934                        errorMessages.add(Bundle.getMessage("VariableNameIsEmpty", v.getName()));
935                        hasErrors = true;
936                    }
937                    if (! SymbolTable.validateName(v.getName())) {
938                        errorMessages.add(Bundle.getMessage("VariableNameIsNotValid", v.getName()));
939                        hasErrors = true;
940                    }
941                }
942
943                if (hasErrors) {
944                    StringBuilder errorMsg = new StringBuilder();
945                    for (String s : errorMessages) {
946                        if (errorMsg.length() > 0) errorMsg.append("<br>");
947                        errorMsg.append(s);
948                    }
949                    JmriJOptionPane.showMessageDialog(null,
950                            Bundle.getMessage("ValidateErrorMessage", errorMsg),
951                            Bundle.getMessage("ValidateErrorTitle"),
952                            JmriJOptionPane.ERROR_MESSAGE);
953
954                } else {
955                    _treePane._femaleRootSocket.unregisterListeners();
956
957                    runOnConditionalNGThreadOrGUIThreadEventually(
958                            _treePane._femaleRootSocket.getConditionalNG(),
959                            () -> {
960
961                        maleSocket.clearLocalVariables();
962                        for (SymbolTable.VariableData variableData : _localVariableTableModel.getVariables()) {
963                            maleSocket.addLocalVariable(variableData);
964                        }
965
966                        ThreadingUtil.runOnGUIEventually(() -> {
967                            _editLocalVariablesDialog.dispose();
968                            _editLocalVariablesDialog = null;
969                            if (_treePane._femaleRootSocket.isActive()) {
970                                _treePane._femaleRootSocket.registerListeners();
971                            }
972                            for (TreeModelListener l : _treePane.femaleSocketTreeModel.listeners) {
973                                TreeModelEvent tme = new TreeModelEvent(
974                                        femaleSocket,
975                                        path.getPath()
976                                );
977                                l.treeNodesChanged(tme);
978                            }
979                            _treePane._tree.updateUI();
980                        });
981                        setPopupMenuLock(false);
982                    });
983                }
984            });
985//            _edit.setToolTipText(Bundle.getMessage("EditButtonHint"));  // NOI18N
986
987//            makeAddEditFrame(false, femaleSocket, _editSwingConfiguratorInterface, _edit);  // NOI18N
988
989            _editLocalVariablesDialog = new JDialog(
990                    this,
991                    Bundle.getMessage(
992                            "EditLocalVariablesDialogTitle",
993                            femaleSocket.getLongDescription()),
994                    false);
995    //        frame.addHelpMenu(
996    //                "package.jmri.jmrit.logixng.tools.swing.ConditionalNGAddEdit", true);     // NOI18N
997            Container contentPanel = _editLocalVariablesDialog.getContentPane();
998            contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
999
1000            JTable table = new JTable();
1001            _localVariableTableModel = new LocalVariableTableModel(maleSocket);
1002            table.setModel(_localVariableTableModel);
1003            table.setDefaultRenderer(InitialValueType.class,
1004                    new LocalVariableTableModel.TypeCellRenderer());
1005            table.setDefaultEditor(InitialValueType.class,
1006                    new LocalVariableTableModel.TypeCellEditor());
1007            table.setDefaultRenderer(LocalVariableTableModel.Menu.class,
1008                    new LocalVariableTableModel.MenuCellRenderer());
1009            table.setDefaultEditor(LocalVariableTableModel.Menu.class,
1010                    new LocalVariableTableModel.MenuCellEditor(table, _localVariableTableModel));
1011            _localVariableTableModel.setColumnForMenu(table);
1012            JScrollPane scrollpane = new JScrollPane(table);
1013            scrollpane.setPreferredSize(new Dimension(400, 200));
1014            contentPanel.add(scrollpane);
1015
1016            // set up create and cancel buttons
1017            JPanel buttonPanel = new JPanel();
1018            buttonPanel.setLayout(new FlowLayout());
1019
1020            // Function help
1021            JButton showFunctionHelp = new JButton(Bundle.getMessage("ButtonFunctionHelp"));    // NOI18N
1022            buttonPanel.add(showFunctionHelp);
1023            showFunctionHelp.addActionListener((ActionEvent e) -> {
1024                InstanceManager.getDefault(FunctionsHelpDialog.class).showDialog();
1025            });
1026//            showFunctionHelp.setToolTipText("FunctionHelpButtonHint");      // NOI18N
1027
1028            // Add local variable
1029            JButton add = new JButton(Bundle.getMessage("TableAddVariable"));
1030            buttonPanel.add(add);
1031            add.addActionListener((ActionEvent e) -> {
1032                _localVariableTableModel.add();
1033            });
1034
1035            // Cancel
1036            JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));    // NOI18N
1037            buttonPanel.add(cancel);
1038            cancel.addActionListener((ActionEvent e) -> {
1039                _editLocalVariablesDialog.setVisible(false);
1040                _editLocalVariablesDialog.dispose();
1041                _editLocalVariablesDialog = null;
1042                setPopupMenuLock(false);
1043            });
1044            cancel.setToolTipText(Bundle.getMessage("LogixNG_CancelButtonHint"));      // NOI18N
1045
1046            buttonPanel.add(_edit);
1047            _editLocalVariablesDialog.getRootPane().setDefaultButton(_edit);
1048
1049            _editLocalVariablesDialog.addWindowListener(new java.awt.event.WindowAdapter() {
1050                @Override
1051                public void windowClosing(java.awt.event.WindowEvent e) {
1052                    _editLocalVariablesDialog.setVisible(false);
1053                    _editLocalVariablesDialog.dispose();
1054                    _editLocalVariablesDialog = null;
1055                    setPopupMenuLock(false);
1056                }
1057            });
1058
1059            contentPanel.add(buttonPanel);
1060
1061            _autoSystemName.addItemListener((ItemEvent e) -> {
1062                autoSystemName();
1063            });
1064    //        addLogixNGFrame.setLocationRelativeTo(component);
1065            _editLocalVariablesDialog.pack();
1066            _editLocalVariablesDialog.setLocationRelativeTo(null);
1067
1068            _editLocalVariablesDialog.setVisible(true);
1069        }
1070    }
1071
1072    /**
1073     * Respond to the Change user name menu choice in the popup menu.
1074     *
1075     * @param femaleSocket the female socket
1076     * @param path the path to the item the user has clicked on
1077     */
1078    final protected void changeUsername(FemaleSocket femaleSocket, TreePath path) {
1079        // possible change
1080        _showReminder = true;
1081        setPopupMenuLock(true);
1082        // make an Edit Frame
1083        if (_changeUsernameDialog == null) {
1084            MaleSocket maleSocket = femaleSocket.getConnectedSocket();
1085
1086            // Edit ConditionalNG
1087            _edit = new JButton(Bundle.getMessage("ButtonOK"));  // NOI18N
1088            _edit.addActionListener((ActionEvent e) -> {
1089
1090                boolean hasErrors = false;
1091                if (hasErrors) {
1092                    String errorMsg = "";
1093                    JmriJOptionPane.showMessageDialog(null,
1094                            Bundle.getMessage("ValidateErrorMessage", errorMsg),
1095                            Bundle.getMessage("ValidateErrorTitle"),
1096                            JmriJOptionPane.ERROR_MESSAGE);
1097
1098                } else {
1099                    _treePane._femaleRootSocket.unregisterListeners();
1100
1101                    runOnConditionalNGThreadOrGUIThreadEventually(
1102                            _treePane._femaleRootSocket.getConditionalNG(),
1103                            () -> {
1104
1105                        String username = _usernameField.getText();
1106                        if (username.equals("")) username = null;
1107
1108                        // Only change user name if it's changed
1109                        if (((username == null) && (maleSocket.getUserName() != null))
1110                                || ((username != null) && !username.equals(maleSocket.getUserName()))) {
1111
1112                            if (username != null) {
1113                                NamedBean nB = maleSocket.getManager().getByUserName(username);
1114                                if (nB != null) {
1115                                    String uname = username;
1116                                    ThreadingUtil.runOnGUIEventually(() -> {
1117                                        log.error("User name is not unique {}", uname);
1118                                        String msg = Bundle.getMessage("WarningUserName", new Object[]{("" + uname)});
1119                                        JmriJOptionPane.showMessageDialog(null, msg,
1120                                                Bundle.getMessage("WarningTitle"),
1121                                                JmriJOptionPane.ERROR_MESSAGE);
1122                                    });
1123                                    username = null;
1124                                }
1125                            }
1126
1127                            maleSocket.setUserName(username);
1128
1129                            MaleSocket m = maleSocket;
1130                            while (! (m instanceof NamedBean)) m = (MaleSocket) m.getObject();
1131
1132                            NamedBeanHandleManager nbMan = InstanceManager.getDefault(NamedBeanHandleManager.class);
1133                            if (nbMan.inUse(maleSocket.getSystemName(), (NamedBean)m)) {
1134                                String msg = Bundle.getMessage("UpdateToUserName", new Object[]{maleSocket.getManager().getBeanTypeHandled(), username, maleSocket.getSystemName()});
1135                                int optionPane = JmriJOptionPane.showConfirmDialog(null,
1136                                        msg, Bundle.getMessage("UpdateToUserNameTitle"),
1137                                        JmriJOptionPane.YES_NO_OPTION);
1138                                if (optionPane == JmriJOptionPane.YES_OPTION) {
1139                                    //This will update the bean reference from the systemName to the userName
1140                                    try {
1141                                        nbMan.updateBeanFromSystemToUser((NamedBean)m);
1142                                    } catch (JmriException ex) {
1143                                        //We should never get an exception here as we already check that the username is not valid
1144                                        log.error("Impossible exception setting user name", ex);
1145                                    }
1146                                }
1147                            }
1148                        }
1149
1150                        ThreadingUtil.runOnGUIEventually(() -> {
1151                            if (_treePane._femaleRootSocket.isActive()) {
1152                                _treePane._femaleRootSocket.registerListeners();
1153                            }
1154                            _changeUsernameDialog.dispose();
1155                            _changeUsernameDialog = null;
1156                            for (TreeModelListener l : _treePane.femaleSocketTreeModel.listeners) {
1157                                TreeModelEvent tme = new TreeModelEvent(
1158                                        femaleSocket,
1159                                        path.getPath()
1160                                );
1161                                l.treeNodesChanged(tme);
1162                            }
1163                            _treePane._tree.updateUI();
1164                        });
1165                        setPopupMenuLock(false);
1166                    });
1167                }
1168            });
1169//            _edit.setToolTipText(Bundle.getMessage("EditButtonHint"));  // NOI18N
1170
1171//            makeAddEditFrame(false, femaleSocket, _editSwingConfiguratorInterface, _edit);  // NOI18N
1172
1173            _changeUsernameDialog = new JDialog(
1174                    this,
1175                    Bundle.getMessage(
1176                            "EditLocalVariablesDialogTitle",
1177                            femaleSocket.getLongDescription()),
1178                    false);
1179    //        frame.addHelpMenu(
1180    //                "package.jmri.jmrit.logixng.tools.swing.ConditionalNGAddEdit", true);     // NOI18N
1181            Container contentPanel = _changeUsernameDialog.getContentPane();
1182            contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
1183
1184//            JPanel tablePanel = new JPanel();
1185
1186            JLabel usernameLabel = new JLabel("Username");
1187            _usernameField.setText(maleSocket.getUserName());
1188
1189            contentPanel.add(usernameLabel);
1190            contentPanel.add(_usernameField);
1191
1192            // set up create and cancel buttons
1193            JPanel buttonPanel = new JPanel();
1194            buttonPanel.setLayout(new FlowLayout());
1195
1196            // Cancel
1197            JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));    // NOI18N
1198            buttonPanel.add(cancel);
1199            cancel.addActionListener((ActionEvent e) -> {
1200                _changeUsernameDialog.setVisible(false);
1201                _changeUsernameDialog.dispose();
1202                _changeUsernameDialog = null;
1203                setPopupMenuLock(false);
1204            });
1205            cancel.setToolTipText(Bundle.getMessage("LogixNG_CancelButtonHint"));      // NOI18N
1206
1207            buttonPanel.add(_edit);
1208            _changeUsernameDialog.getRootPane().setDefaultButton(_edit);
1209
1210            _changeUsernameDialog.addWindowListener(new java.awt.event.WindowAdapter() {
1211                @Override
1212                public void windowClosing(java.awt.event.WindowEvent e) {
1213                    _changeUsernameDialog.setVisible(false);
1214                    _changeUsernameDialog.dispose();
1215                    _changeUsernameDialog = null;
1216                    setPopupMenuLock(false);
1217                }
1218            });
1219
1220            contentPanel.add(buttonPanel);
1221
1222            _autoSystemName.addItemListener((ItemEvent e) -> {
1223                autoSystemName();
1224            });
1225    //        addLogixNGFrame.setLocationRelativeTo(component);
1226            _changeUsernameDialog.pack();
1227            _changeUsernameDialog.setLocationRelativeTo(null);
1228
1229            _changeUsernameDialog.setVisible(true);
1230        }
1231    }
1232
1233    /**
1234     * Enable/disable fields for data entry when user selects to have system
1235     * name automatically generated.
1236     */
1237    final protected void autoSystemName() {
1238        if (_autoSystemName.isSelected()) {
1239            _systemName.setEnabled(false);
1240            _sysNameLabel.setEnabled(false);
1241        } else {
1242            _systemName.setEnabled(true);
1243            _sysNameLabel.setEnabled(true);
1244        }
1245    }
1246
1247    /**
1248     * Respond to the Cancel button in Rename socket window.
1249     * <p>
1250     * Note: Also get there if the user closes the Rename socket window.
1251     *
1252     * @param e The event heard
1253     */
1254    final protected void cancelRenameSocketPressed(ActionEvent e) {
1255        _renameSocketDialog.setVisible(false);
1256        _renameSocketDialog.dispose();
1257        _renameSocketDialog = null;
1258        setPopupMenuLock(false);
1259        this.setVisible(true);
1260    }
1261
1262    /**
1263     * Respond to the Cancel button in Add ConditionalNG window.
1264     * <p>
1265     * Note: Also get there if the user closes the Add ConditionalNG window.
1266     *
1267     * @param e The event heard
1268     */
1269    final protected void cancelCreateItem(ActionEvent e) {
1270        _addItemDialog.setVisible(false);
1271        _addSwingConfiguratorInterface.dispose();
1272        _addItemDialog.dispose();
1273        _addItemDialog = null;
1274        setPopupMenuLock(false);
1275//        _inCopyMode = false;
1276        this.setVisible(true);
1277    }
1278
1279
1280    /**
1281     * Respond to the Cancel button in Add ConditionalNG window.
1282     * <p>
1283     * Note: Also get there if the user closes the Add ConditionalNG window.
1284     *
1285     * @param e The event heard
1286     */
1287    final protected void cancelEditPressed(ActionEvent e) {
1288        for (Map.Entry<SwingConfiguratorInterface, Base> entry : _swingConfiguratorInterfaceList) {
1289            // Abort if we cannot close the dialog
1290            if (!entry.getKey().canClose()) return;
1291        }
1292
1293        _editActionExpressionDialog.setVisible(false);
1294
1295        for (Map.Entry<SwingConfiguratorInterface, Base> entry : _swingConfiguratorInterfaceList) {
1296            entry.getKey().dispose();
1297        }
1298        _editActionExpressionDialog.dispose();
1299        _editActionExpressionDialog = null;
1300        setPopupMenuLock(false);
1301        this.setVisible(true);
1302    }
1303
1304
1305    protected void executeEvaluate(SwingConfiguratorInterface swi, MaleSocket maleSocket) {
1306        swi.executeEvaluate(maleSocket);
1307    }
1308
1309    private boolean itemIsSystem(FemaleSocket femaleSocket) {
1310        return (femaleSocket.isConnected())
1311                && femaleSocket.getConnectedSocket().isSystem();
1312    }
1313
1314    private boolean parentIsSystem(FemaleSocket femaleSocket) {
1315        Base parent = femaleSocket.getParent();
1316        while ((parent != null) && !(femaleSocket.getParent() instanceof MaleSocket)) {
1317            parent = parent.getParent();
1318        }
1319        return (parent != null) && ((MaleSocket)parent).isSystem();
1320    }
1321
1322    /**
1323     * Asks the user if edit a system node.
1324     * @return true if not edit system node, else return false
1325     */
1326    private boolean abortEditAboutSystem(Base b) {
1327        int result = JmriJOptionPane.showConfirmDialog(
1328                this,
1329                Bundle.getMessage("TreeEditor_ChangeSystemNode"),
1330                b.getLongDescription(),
1331                JmriJOptionPane.YES_NO_OPTION);
1332
1333        return ( result != JmriJOptionPane.YES_OPTION );
1334    }
1335
1336    private void editItem(FemaleSocket femaleSocket, TreePath path) {
1337        if (itemIsSystem(femaleSocket) && abortEditAboutSystem(femaleSocket.getConnectedSocket())) {
1338            return;
1339        }
1340        editPressed(femaleSocket, path);
1341    }
1342
1343    private void removeItem(FemaleSocket femaleSocket, TreePath path) {
1344        if ((parentIsSystem(femaleSocket) || itemIsSystem(femaleSocket)) && abortEditAboutSystem(femaleSocket.getConnectedSocket())) {
1345            return;
1346        }
1347        DeleteBeanWorker worker = new DeleteBeanWorker(femaleSocket, path);
1348        worker.execute();
1349    }
1350
1351    private void cutItem(FemaleSocket femaleSocket, TreePath path) {
1352        if ((parentIsSystem(femaleSocket) || itemIsSystem(femaleSocket)) && abortEditAboutSystem(femaleSocket.getConnectedSocket())) {
1353            return;
1354        }
1355
1356        if (femaleSocket.isConnected()) {
1357            _treePane._femaleRootSocket.unregisterListeners();
1358
1359            runOnConditionalNGThreadOrGUIThreadEventually(
1360                    _treePane._femaleRootSocket.getConditionalNG(),
1361                    () -> {
1362                Clipboard clipboard =
1363                        InstanceManager.getDefault(LogixNG_Manager.class).getClipboard();
1364                List<String> errors = new ArrayList<>();
1365                MaleSocket maleSocket = femaleSocket.getConnectedSocket();
1366                femaleSocket.disconnect();
1367                if (!clipboard.add(maleSocket, errors)) {
1368                    JmriJOptionPane.showMessageDialog(this,
1369                            String.join("<br>", errors),
1370                            Bundle.getMessage("TitleError"),
1371                            JmriJOptionPane.ERROR_MESSAGE);
1372                }
1373                ThreadingUtil.runOnGUIEventually(() -> {
1374                    maleSocket.forEntireTree((Base b) -> {
1375                        b.removePropertyChangeListener(_treePane);
1376                        if (_clipboardEditor != null) {
1377                            b.addPropertyChangeListener(_clipboardEditor._treePane);
1378                        }
1379                    });
1380                    _treePane._femaleRootSocket.registerListeners();
1381                    _treePane.updateTree(femaleSocket, path.getPath());
1382                });
1383            });
1384        } else {
1385            log.error("_currentFemaleSocket is not connected");
1386        }
1387    }
1388
1389    private void copyItem(FemaleSocket femaleSocket) {
1390        if ((parentIsSystem(femaleSocket) || itemIsSystem(femaleSocket)) && abortEditAboutSystem(femaleSocket.getConnectedSocket())) {
1391            return;
1392        }
1393
1394        if (femaleSocket.isConnected()) {
1395           _treePane._femaleRootSocket.unregisterListeners();
1396
1397           runOnConditionalNGThreadOrGUIThreadEventually(
1398                   _treePane._femaleRootSocket.getConditionalNG(),
1399                   () -> {
1400               Clipboard clipboard =
1401                       InstanceManager.getDefault(LogixNG_Manager.class).getClipboard();
1402               Map<String, String> systemNames = new HashMap<>();
1403               Map<String, String> userNames = new HashMap<>();
1404               MaleSocket maleSocket = null;
1405               try {
1406                   maleSocket = (MaleSocket) femaleSocket
1407                           .getConnectedSocket()
1408                           .getDeepCopy(systemNames, userNames);
1409                   List<String> errors = new ArrayList<>();
1410                   if (!clipboard.add(
1411                           maleSocket,
1412                           errors)) {
1413                       JmriJOptionPane.showMessageDialog(this,
1414                               String.join("<br>", errors),
1415                               Bundle.getMessage("TitleError"),
1416                               JmriJOptionPane.ERROR_MESSAGE);
1417                   }
1418               } catch (JmriException ex) {
1419                   log.error("getDeepCopy thrown exception: {}", ex, ex);
1420                   ThreadingUtil.runOnGUIEventually(() -> {
1421                       JmriJOptionPane.showMessageDialog(null,
1422                               "An exception has occured: "+ex.getMessage(),
1423                               "An error has occured",
1424                               JmriJOptionPane.ERROR_MESSAGE);
1425                   });
1426               }
1427               if (maleSocket != null) {
1428                   MaleSocket socket = maleSocket;
1429                   ThreadingUtil.runOnGUIEventually(() -> {
1430                       socket.forEntireTree((Base b) -> {
1431                           if (_clipboardEditor != null) {
1432                               b.addPropertyChangeListener(_clipboardEditor._treePane);
1433                           }
1434                       });
1435                   });
1436               }
1437           });
1438
1439           _treePane._femaleRootSocket.registerListeners();
1440       } else {
1441           log.error("_currentFemaleSocket is not connected");
1442       }
1443    }
1444
1445    private void pasteItem(FemaleSocket femaleSocket, TreePath path) {
1446        if (parentIsSystem(femaleSocket) && abortEditAboutSystem(femaleSocket.getParent())) {
1447            return;
1448        }
1449
1450        if (! femaleSocket.isConnected()) {
1451            _treePane._femaleRootSocket.unregisterListeners();
1452
1453            runOnConditionalNGThreadOrGUIThreadEventually(
1454                    _treePane._femaleRootSocket.getConditionalNG(),
1455                    () -> {
1456                Clipboard clipboard =
1457                        InstanceManager.getDefault(LogixNG_Manager.class).getClipboard();
1458                try {
1459                    if (clipboard.getTopItem() == null) {
1460                        return;
1461                    }
1462                    if (!femaleSocket.isCompatible(clipboard.getTopItem())) {
1463                        log.error("Top item on clipboard is not compatible with the female socket");
1464                        return;
1465                    }
1466                    femaleSocket.connect(clipboard.fetchTopItem());
1467                    List<String> errors = new ArrayList<>();
1468                    if (!femaleSocket.setParentForAllChildren(errors)) {
1469                        JmriJOptionPane.showMessageDialog(this,
1470                                String.join("<br>", errors),
1471                                Bundle.getMessage("TitleError"),
1472                                JmriJOptionPane.ERROR_MESSAGE);
1473                    }
1474                } catch (SocketAlreadyConnectedException ex) {
1475                    log.error("item cannot be connected", ex);
1476                }
1477                ThreadingUtil.runOnGUIEventually(() -> {
1478                    _treePane._femaleRootSocket.forEntireTree((Base b) -> {
1479                        // Remove the listener if it is already
1480                        // added so we don't end up with duplicate
1481                        // listeners.
1482                        b.removePropertyChangeListener(_treePane);
1483                        b.addPropertyChangeListener(_treePane);
1484                    });
1485                    _treePane._femaleRootSocket.registerListeners();
1486                    _treePane.updateTree(femaleSocket, path.getPath());
1487                });
1488            });
1489        } else {
1490            log.error("_currentFemaleSocket is connected");
1491        }
1492    }
1493
1494    private void pasteCopy(FemaleSocket femaleSocket, TreePath path) {
1495        if (parentIsSystem(femaleSocket) && abortEditAboutSystem(femaleSocket.getParent())) {
1496            return;
1497        }
1498
1499        if (! femaleSocket.isConnected()) {
1500            _treePane._femaleRootSocket.unregisterListeners();
1501
1502            runOnConditionalNGThreadOrGUIThreadEventually(
1503                    _treePane._femaleRootSocket.getConditionalNG(),
1504                    () -> {
1505                Clipboard clipboard =
1506                        InstanceManager.getDefault(LogixNG_Manager.class).getClipboard();
1507
1508                if (clipboard.getTopItem() == null) {
1509                    return;
1510                }
1511                if (!femaleSocket.isCompatible(clipboard.getTopItem())) {
1512                    log.error("Top item on clipboard is not compatible with the female socket");
1513                    return;
1514                }
1515                Map<String, String> systemNames = new HashMap<>();
1516                Map<String, String> userNames = new HashMap<>();
1517                MaleSocket maleSocket = null;
1518                try {
1519                    maleSocket = (MaleSocket) clipboard.getTopItem()
1520                            .getDeepCopy(systemNames, userNames);
1521                } catch (JmriException ex) {
1522                    log.error("getDeepCopy thrown exception: {}", ex, ex);
1523                    ThreadingUtil.runOnGUIEventually(() -> {
1524                        JmriJOptionPane.showMessageDialog(null,
1525                                "An exception has occured: "+ex.getMessage(),
1526                                "An error has occured",
1527                                JmriJOptionPane.ERROR_MESSAGE);
1528                    });
1529                }
1530                if (maleSocket != null) {
1531                    try {
1532                        femaleSocket.connect(maleSocket);
1533                        List<String> errors = new ArrayList<>();
1534                        if (!femaleSocket.setParentForAllChildren(errors)) {
1535                            JmriJOptionPane.showMessageDialog(this,
1536                                    String.join("<br>", errors),
1537                                    Bundle.getMessage("TitleError"),
1538                                    JmriJOptionPane.ERROR_MESSAGE);
1539                        }
1540                    } catch (SocketAlreadyConnectedException ex) {
1541                        log.error("item cannot be connected", ex);
1542                    }
1543                    ThreadingUtil.runOnGUIEventually(() -> {
1544                        _treePane._femaleRootSocket.forEntireTree((Base b) -> {
1545                            // Remove the listener if it is already
1546                            // added so we don't end up with duplicate
1547                            // listeners.
1548                            b.removePropertyChangeListener(_treePane);
1549                            b.addPropertyChangeListener(_treePane);
1550                        });
1551                        _treePane._femaleRootSocket.registerListeners();
1552                        _treePane.updateTree(femaleSocket, path.getPath());
1553                    });
1554                }
1555            });
1556        } else {
1557            log.error("_currentFemaleSocket is connected");
1558        }
1559    }
1560
1561    private void doIt(String command, FemaleSocket femaleSocket, TreePath path) {
1562        Base parent = femaleSocket.getParent();
1563        while ((parent != null) && !(femaleSocket.getParent() instanceof MaleSocket)) {
1564            parent = parent.getParent();
1565        }
1566        boolean parentIsSystem = (parent != null) && ((MaleSocket)parent).isSystem();
1567        boolean itemIsSystem = itemIsSystem(femaleSocket);
1568
1569        switch (command) {
1570            case ACTION_COMMAND_RENAME_SOCKET:
1571                if (parentIsSystem && abortEditAboutSystem(femaleSocket.getParent())) break;
1572                renameSocketPressed(femaleSocket, path);
1573                break;
1574
1575            case ACTION_COMMAND_EDIT:
1576                editItem(femaleSocket, path);
1577                break;
1578
1579            case ACTION_COMMAND_REMOVE:
1580                removeItem(femaleSocket, path);
1581                break;
1582
1583            case ACTION_COMMAND_CUT:
1584                cutItem(femaleSocket, path);
1585                break;
1586
1587            case ACTION_COMMAND_COPY:
1588                copyItem(femaleSocket);
1589                break;
1590
1591            case ACTION_COMMAND_PASTE:
1592                pasteItem(femaleSocket, path);
1593                break;
1594
1595            case ACTION_COMMAND_PASTE_COPY:
1596                pasteCopy(femaleSocket, path);
1597                break;
1598
1599            case ACTION_COMMAND_ENABLE:
1600                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1601
1602                femaleSocket.getConnectedSocket().setEnabled(true);
1603                runOnConditionalNGThreadOrGUIThreadEventually(
1604                        _treePane._femaleRootSocket.getConditionalNG(),
1605                        () -> {
1606                    ThreadingUtil.runOnGUIEventually(() -> {
1607                        _treePane._femaleRootSocket.unregisterListeners();
1608                        _treePane.updateTree(femaleSocket, path.getPath());
1609                        _treePane._femaleRootSocket.registerListeners();
1610                    });
1611                });
1612                break;
1613
1614            case ACTION_COMMAND_DISABLE:
1615                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1616
1617                femaleSocket.getConnectedSocket().setEnabled(false);
1618                runOnConditionalNGThreadOrGUIThreadEventually(
1619                        _treePane._femaleRootSocket.getConditionalNG(),
1620                        () -> {
1621                    ThreadingUtil.runOnGUIEventually(() -> {
1622                        _treePane._femaleRootSocket.unregisterListeners();
1623                        _treePane.updateTree(femaleSocket, path.getPath());
1624                        _treePane._femaleRootSocket.registerListeners();
1625                    });
1626                });
1627                break;
1628
1629            case ACTION_COMMAND_LOCK:
1630                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1631
1632                femaleSocket.forEntireTree((item) -> {
1633                    if (item instanceof MaleSocket) {
1634                        ((MaleSocket)item).setLocked(true);
1635                    }
1636                });
1637                _treePane.updateTree(femaleSocket, path.getPath());
1638                break;
1639
1640            case ACTION_COMMAND_UNLOCK:
1641                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1642
1643                femaleSocket.forEntireTree((item) -> {
1644                    if (item instanceof MaleSocket) {
1645                        ((MaleSocket)item).setLocked(false);
1646                    }
1647                });
1648                _treePane.updateTree(femaleSocket, path.getPath());
1649                break;
1650
1651            case ACTION_COMMAND_LOCAL_VARIABLES:
1652                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1653                editLocalVariables(femaleSocket, path);
1654                break;
1655
1656            case ACTION_COMMAND_CHANGE_USERNAME:
1657                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1658                changeUsername(femaleSocket, path);
1659                break;
1660
1661            case ACTION_COMMAND_EXECUTE_EVALUATE:
1662                Base object = femaleSocket.getConnectedSocket();
1663                if (object == null) throw new NullPointerException("object is null");
1664                while (object instanceof MaleSocket) {
1665                    object = ((MaleSocket)object).getObject();
1666                }
1667                SwingConfiguratorInterface swi =
1668                        SwingTools.getSwingConfiguratorForClass(object.getClass());
1669                executeEvaluate(swi, femaleSocket.getConnectedSocket());
1670                break;
1671
1672/*
1673            case ACTION_COMMAND_EXPAND_TREE:
1674                // jtree expand sub tree
1675                // https://stackoverflow.com/questions/15210979/how-do-i-auto-expand-a-jtree-when-setting-a-new-treemodel
1676                // https://www.tutorialspoint.com/how-to-expand-jtree-row-to-display-all-the-nodes-and-child-nodes-in-java
1677                // To expand all rows, do this:
1678                for (int i = 0; i < tree.getRowCount(); i++) {
1679                    tree.expandRow(i);
1680                }
1681
1682                tree.expandPath(_currentPath);
1683                tree.updateUI();
1684                break;
1685*/
1686            default:
1687                // Check if the action is a female socket operation
1688                if (!checkFemaleSocketOperation(femaleSocket, parentIsSystem, itemIsSystem, command)) {
1689                    log.error("e.getActionCommand() returns unknown value {}", command);
1690                }
1691        }
1692    }
1693
1694    private boolean checkFemaleSocketOperation(
1695            FemaleSocket femaleSocket,
1696            boolean parentIsSystem,
1697            boolean itemIsSystem,
1698            String command) {
1699
1700        for (FemaleSocketOperation oper : FemaleSocketOperation.values()) {
1701            if (oper.name().equals(command)) {
1702                if ((parentIsSystem || itemIsSystem) && abortEditAboutSystem(femaleSocket.getParent())) return true;
1703                femaleSocket.doSocketOperation(oper);
1704                return true;
1705            }
1706        }
1707        return false;
1708    }
1709
1710    private boolean parentIsLocked(FemaleSocket femaleSocket) {
1711        Base parent = femaleSocket.getParent();
1712        while ((parent != null) && !(parent instanceof MaleSocket)) {
1713            parent = parent.getParent();
1714        }
1715        return (parent != null) && ((MaleSocket)parent).isLocked();
1716    }
1717
1718    protected final class PopupMenu extends JPopupMenu implements ActionListener {
1719
1720        private final JTree _tree;
1721//        private final FemaleSocketTreeModel _model;
1722        private final FemaleSocket _currentFemaleSocket;
1723        private final TreePath _currentPath;
1724
1725        private JMenuItem menuItemRenameSocket;
1726        private JMenuItem menuItemRemove;
1727        private JMenuItem menuItemCut;
1728        private JMenuItem menuItemCopy;
1729        private JMenuItem menuItemPaste;
1730        private JMenuItem menuItemPasteCopy;
1731        private final Map<FemaleSocketOperation, JMenuItem> menuItemFemaleSocketOperation
1732                = new HashMap<>();
1733        private JMenuItem menuItemEnable;
1734        private JMenuItem menuItemDisable;
1735        private JMenuItem menuItemLock;
1736        private JMenuItem menuItemUnlock;
1737        private JMenuItem menuItemLocalVariables;
1738        private JMenuItem menuItemChangeUsername;
1739        private JMenuItem menuItemExecuteEvaluate;
1740//        private JMenuItem menuItemExpandTree;
1741
1742        private final boolean _isConnected;
1743        private final boolean _canConnectFromClipboard;
1744        private final boolean _disableForRoot;
1745        private final boolean _isLocked;
1746        private final boolean _parentIsLocked;
1747
1748
1749        PopupMenu(int x, int y, FemaleSocket femaleSocket, TreePath path, boolean onlyAddItems) {
1750
1751            if (_treePane._tree == null) throw new IllegalArgumentException("_tree is null");
1752
1753            _tree = _treePane._tree;
1754
1755            _currentFemaleSocket = femaleSocket;
1756            _currentPath = path;
1757            _isConnected = femaleSocket.isConnected();
1758
1759            Clipboard clipboard = InstanceManager.getDefault(LogixNG_Manager.class).getClipboard();
1760
1761            MaleSocket topItem = clipboard.getTopItem();
1762
1763            _canConnectFromClipboard =
1764                    topItem != null
1765                    && femaleSocket.isCompatible(topItem)
1766                    && !femaleSocket.isAncestor(topItem);
1767
1768            _disableForRoot = _disableRootRemoveCutCopy
1769                    && (_currentFemaleSocket == _treePane._femaleRootSocket);
1770
1771            _isLocked = _isConnected && femaleSocket.getConnectedSocket().isLocked();
1772
1773            _parentIsLocked = parentIsLocked(femaleSocket);
1774
1775            if (onlyAddItems) {
1776                addNewItemTypes(this);
1777            } else {
1778                if (_disableRootPopup
1779                        && (_currentFemaleSocket == _treePane._femaleRootSocket)) {
1780                    JmriJOptionPane.showMessageDialog(null,
1781                            Bundle.getMessage("TreeEditor_RootHasNoPopupMenu"),
1782                            Bundle.getMessage("TreeEditor_Info"),
1783                            JmriJOptionPane.ERROR_MESSAGE);
1784                    return;
1785                }
1786
1787                menuItemRenameSocket = new JMenuItem(Bundle.getMessage("PopupMenuRenameSocket"));
1788                menuItemRenameSocket.addActionListener(this);
1789                menuItemRenameSocket.setActionCommand(ACTION_COMMAND_RENAME_SOCKET);
1790                add(menuItemRenameSocket);
1791                addSeparator();
1792
1793                if (!_isConnected && !_parentIsLocked) {
1794                    JMenu addMenu = new JMenu(Bundle.getMessage("PopupMenuAdd"));
1795//                    addMenu.setMnemonic(KeyEvent.VK_F);
1796//                    addMenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1797                    addNewItemTypes(addMenu);
1798                    add(addMenu);
1799                }
1800
1801                if (_isConnected && !_isLocked) {
1802                    JMenuItem menuItemEdit = new JMenuItem(Bundle.getMessage("PopupMenuEdit"));
1803                    menuItemEdit.addActionListener(this);
1804                    menuItemEdit.setActionCommand(ACTION_COMMAND_EDIT);
1805                    menuItemEdit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1806                    add(menuItemEdit);
1807                }
1808                addSeparator();
1809                menuItemRemove = new JMenuItem(Bundle.getMessage("PopupMenuRemove"));
1810                menuItemRemove.addActionListener(this);
1811                menuItemRemove.setActionCommand(ACTION_COMMAND_REMOVE);
1812                menuItemRemove.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1813                add(menuItemRemove);
1814                addSeparator();
1815                menuItemCut = new JMenuItem(Bundle.getMessage("PopupMenuCut"));
1816                menuItemCut.addActionListener(this);
1817                menuItemCut.setActionCommand(ACTION_COMMAND_CUT);
1818                menuItemCut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1819                add(menuItemCut);
1820                menuItemCopy = new JMenuItem(Bundle.getMessage("PopupMenuCopy"));
1821                menuItemCopy.addActionListener(this);
1822                menuItemCopy.setActionCommand(ACTION_COMMAND_COPY);
1823                menuItemCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1824                add(menuItemCopy);
1825                menuItemPaste = new JMenuItem(Bundle.getMessage("PopupMenuPaste"));
1826                menuItemPaste.addActionListener(this);
1827                menuItemPaste.setActionCommand(ACTION_COMMAND_PASTE);
1828                menuItemPaste.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1829                add(menuItemPaste);
1830                menuItemPasteCopy = new JMenuItem(Bundle.getMessage("PopupMenuPasteCopy"));
1831                menuItemPasteCopy.addActionListener(this);
1832                menuItemPasteCopy.setActionCommand(ACTION_COMMAND_PASTE_COPY);
1833                menuItemPasteCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V,
1834                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx() + InputEvent.SHIFT_DOWN_MASK));
1835                add(menuItemPasteCopy);
1836                addSeparator();
1837
1838                for (FemaleSocketOperation oper : FemaleSocketOperation.values()) {
1839                    JMenuItem menuItem = new JMenuItem(oper.toString());
1840                    menuItem.addActionListener(this);
1841                    menuItem.setActionCommand(oper.name());
1842                    add(menuItem);
1843                    menuItemFemaleSocketOperation.put(oper, menuItem);
1844                    if (oper.hasKey()) {
1845                        menuItem.setAccelerator(KeyStroke.getKeyStroke(
1846                                oper.getKeyCode(), oper.getModifiers()));
1847                    }
1848                }
1849
1850                addSeparator();
1851                menuItemEnable = new JMenuItem(Bundle.getMessage("PopupMenuEnable"));
1852                menuItemEnable.addActionListener(this);
1853                menuItemEnable.setActionCommand(ACTION_COMMAND_ENABLE);
1854                menuItemEnable.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D,
1855                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx() + InputEvent.SHIFT_DOWN_MASK));
1856                add(menuItemEnable);
1857                menuItemDisable = new JMenuItem(Bundle.getMessage("PopupMenuDisable"));
1858                menuItemDisable.addActionListener(this);
1859                menuItemDisable.setActionCommand(ACTION_COMMAND_DISABLE);
1860                menuItemDisable.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D,
1861                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1862                add(menuItemDisable);
1863                menuItemLock = new JMenuItem(Bundle.getMessage("PopupMenuLock"));
1864                menuItemLock.addActionListener(this);
1865                menuItemLock.setActionCommand(ACTION_COMMAND_LOCK);
1866                add(menuItemLock);
1867                menuItemUnlock = new JMenuItem(Bundle.getMessage("PopupMenuUnlock"));
1868                menuItemUnlock.addActionListener(this);
1869                menuItemUnlock.setActionCommand(ACTION_COMMAND_UNLOCK);
1870                add(menuItemUnlock);
1871
1872                addSeparator();
1873                menuItemLocalVariables = new JMenuItem(Bundle.getMessage("PopupMenuLocalVariables"));
1874                menuItemLocalVariables.addActionListener(this);
1875                menuItemLocalVariables.setActionCommand(ACTION_COMMAND_LOCAL_VARIABLES);
1876                add(menuItemLocalVariables);
1877
1878                addSeparator();
1879                menuItemChangeUsername = new JMenuItem(Bundle.getMessage("PopupMenuChangeUsername"));
1880                menuItemChangeUsername.addActionListener(this);
1881                menuItemChangeUsername.setActionCommand(ACTION_COMMAND_CHANGE_USERNAME);
1882                add(menuItemChangeUsername);
1883
1884                if (_enableExecuteEvaluate) {
1885                    addSeparator();
1886                    menuItemExecuteEvaluate = new JMenuItem();  // The text is set later
1887                    menuItemExecuteEvaluate.addActionListener(this);
1888                    menuItemExecuteEvaluate.setActionCommand(ACTION_COMMAND_EXECUTE_EVALUATE);
1889                    add(menuItemExecuteEvaluate);
1890                }
1891    /*
1892                addSeparator();
1893                menuItemExpandTree = new JMenuItem(Bundle.getMessage("PopupMenuExpandTree"));
1894                menuItemExpandTree.addActionListener(this);
1895                menuItemExpandTree.setActionCommand(ACTION_COMMAND_EXPAND_TREE);
1896                add(menuItemExpandTree);
1897    */
1898                setOpaque(true);
1899                setLightWeightPopupEnabled(true);
1900
1901                menuItemRemove.setEnabled(_isConnected && !_isLocked && !_parentIsLocked && !_disableForRoot);
1902                menuItemCut.setEnabled(_isConnected && !_isLocked && !_parentIsLocked && !_disableForRoot);
1903                menuItemCopy.setEnabled(_isConnected && !_disableForRoot);
1904                menuItemPaste.setEnabled(!_isConnected && !_parentIsLocked && _canConnectFromClipboard);
1905                menuItemPasteCopy.setEnabled(!_isConnected && !_parentIsLocked && _canConnectFromClipboard);
1906
1907                if (_isConnected && !_disableForRoot) {
1908                    menuItemEnable.setEnabled(!femaleSocket.getConnectedSocket().isEnabled() && !_isLocked);
1909                    menuItemDisable.setEnabled(femaleSocket.getConnectedSocket().isEnabled() && !_isLocked);
1910                } else {
1911                    menuItemEnable.setEnabled(false);
1912                    menuItemDisable.setEnabled(false);
1913                }
1914
1915                for (FemaleSocketOperation oper : FemaleSocketOperation.values()) {
1916                    JMenuItem menuItem = menuItemFemaleSocketOperation.get(oper);
1917                    menuItem.setEnabled(femaleSocket.isSocketOperationAllowed(oper) && !_parentIsLocked);
1918                }
1919
1920                AtomicBoolean isAnyLocked = new AtomicBoolean(false);
1921                AtomicBoolean isAnyUnlocked = new AtomicBoolean(false);
1922
1923                _currentFemaleSocket.forEntireTree((item) -> {
1924                    if (item instanceof MaleSocket) {
1925                        isAnyLocked.set(isAnyLocked.get() || ((MaleSocket)item).isLocked());
1926                        isAnyUnlocked.set(isAnyUnlocked.get() || !((MaleSocket)item).isLocked());
1927                    }
1928                });
1929                menuItemLock.setEnabled(isAnyUnlocked.get());
1930                menuItemUnlock.setEnabled(isAnyLocked.get());
1931
1932                menuItemLocalVariables.setEnabled(
1933                        femaleSocket.isConnected()
1934                        && femaleSocket.getConnectedSocket().isSupportingLocalVariables()
1935                        && !_isLocked);
1936
1937                menuItemChangeUsername.setEnabled(femaleSocket.isConnected() && !_isLocked);
1938
1939                if (_enableExecuteEvaluate) {
1940                    menuItemExecuteEvaluate.setEnabled(femaleSocket.isConnected());
1941
1942                    if (femaleSocket.isConnected()) {
1943                        Base object = _currentFemaleSocket.getConnectedSocket();
1944                        if (object == null) throw new NullPointerException("object is null");
1945                        while (object instanceof MaleSocket) {
1946                            object = ((MaleSocket)object).getObject();
1947                        }
1948                        menuItemExecuteEvaluate.setText(
1949                                SwingTools.getSwingConfiguratorForClass(object.getClass())
1950                                        .getExecuteEvaluateMenuText());
1951                    }
1952                }
1953            }
1954
1955            show(_tree, x, y);
1956        }
1957
1958        private void addNewItemTypes(Container container) {
1959            Map<Category, List<Class<? extends Base>>> connectableClasses =
1960                    _currentFemaleSocket.getConnectableClasses();
1961            List<Category> list = new ArrayList<>(connectableClasses.keySet());
1962            Collections.sort(list);
1963            for (Category category : list) {
1964                List<SwingConfiguratorInterface> sciList = new ArrayList<>();
1965                List<Class<? extends Base>> classes = connectableClasses.get(category);
1966                if (classes != null && !classes.isEmpty()) {
1967                    for (Class<? extends Base> clazz : classes) {
1968                        SwingConfiguratorInterface sci = SwingTools.getSwingConfiguratorForClass(clazz);
1969                        if (sci != null) {
1970                            sciList.add(sci);
1971                        } else {
1972                            log.error("Class {} has no swing configurator interface", clazz.getName());
1973                        }
1974                    }
1975                }
1976
1977                Collections.sort(sciList);
1978
1979                JMenu categoryMenu = new JMenu(category.toString());
1980                for (SwingConfiguratorInterface sci : sciList) {
1981                    JMenuItem item = new JMenuItem(sci.toString());
1982                    item.addActionListener((e) -> {
1983                        createAddFrame(_currentFemaleSocket, _currentPath, sci);
1984                    });
1985                    categoryMenu.add(item);
1986                }
1987                container.add(categoryMenu);
1988            }
1989        }
1990
1991        @Override
1992        public void actionPerformed(ActionEvent e) {
1993            doIt(e.getActionCommand(), _currentFemaleSocket, _currentPath);
1994        }
1995
1996    }
1997
1998
1999    // This class is copied from BeanTableDataModel
2000    private class DeleteBeanWorker extends SwingWorker<Void, Void> {
2001
2002        private final FemaleSocket _currentFemaleSocket;
2003        private final TreePath _currentPath;
2004        MaleSocket _maleSocket;
2005
2006        public DeleteBeanWorker(FemaleSocket currentFemaleSocket, TreePath currentPath) {
2007            _currentFemaleSocket = currentFemaleSocket;
2008            _currentPath = currentPath;
2009            _maleSocket = _currentFemaleSocket.getConnectedSocket();
2010        }
2011
2012        public int getDisplayDeleteMsg() {
2013            return InstanceManager.getDefault(UserPreferencesManager.class).getMultipleChoiceOption(getClassName(), "deleteInUse");
2014        }
2015
2016        public void setDisplayDeleteMsg(int boo) {
2017            InstanceManager.getDefault(UserPreferencesManager.class).setMultipleChoiceOption(getClassName(), "deleteInUse", boo);
2018        }
2019
2020        public void doDelete() {
2021            _treePane._femaleRootSocket.unregisterListeners();
2022            try {
2023                _currentFemaleSocket.disconnect();
2024                _maleSocket.getManager().deleteBean(_maleSocket, "DoDelete");
2025            } catch (PropertyVetoException e) {
2026                //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto
2027                log.error("Unexpected doDelete failure for {}, {}", _maleSocket, e.getMessage() );
2028            } finally {
2029                if (_treePane._femaleRootSocket.isActive()) {
2030                    _treePane._femaleRootSocket.registerListeners();
2031                }
2032            }
2033        }
2034
2035        /**
2036         * {@inheritDoc}
2037         */
2038        @Override
2039        public Void doInBackground() {
2040            StringBuilder message = new StringBuilder();
2041            try {
2042                _maleSocket.getManager().deleteBean(_maleSocket, "CanDelete");  // NOI18N
2043            } catch (PropertyVetoException e) {
2044                if (e.getPropertyChangeEvent().getPropertyName().equals("DoNotDelete")) { // NOI18N
2045                    log.warn("Do not Delete {}, {}", _maleSocket, e.getMessage());
2046                    message.append(Bundle.getMessage(
2047                            "VetoDeleteBean",
2048                            ((NamedBean)_maleSocket.getObject()).getBeanType(),
2049                            ((NamedBean)_maleSocket.getObject()).getDisplayName(
2050                                    NamedBean.DisplayOptions.USERNAME_SYSTEMNAME),
2051                            e.getMessage()));
2052                    JmriJOptionPane.showMessageDialog(null, message.toString(),
2053                            Bundle.getMessage("WarningTitle"),
2054                            JmriJOptionPane.ERROR_MESSAGE);
2055                    return null;
2056                }
2057                message.append(e.getMessage());
2058            }
2059            List<String> listenerRefs = new ArrayList<>();
2060            _maleSocket.getListenerRefsIncludingChildren(listenerRefs);
2061            int count = listenerRefs.size();
2062            log.debug("Delete with {}", count);
2063            if (getDisplayDeleteMsg() == 0x02 && message.toString().isEmpty()) {
2064                doDelete();
2065            } else {
2066                final JDialog dialog = new JDialog();
2067                dialog.setTitle(Bundle.getMessage("WarningTitle"));
2068                dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
2069                JPanel container = new JPanel();
2070                container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
2071                container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
2072                if (count > 0) { // warn of listeners attached before delete
2073
2074                    String prompt = _maleSocket.getChildCount() > 0 ? "DeleteWithChildrenPrompt" : "DeletePrompt";
2075                    JLabel question = new JLabel(Bundle.getMessage(
2076                            prompt,
2077                            ((NamedBean)_maleSocket.getObject())
2078                                    .getDisplayName(NamedBean.DisplayOptions.USERNAME_SYSTEMNAME)));
2079                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
2080                    container.add(question);
2081
2082                    ArrayList<String> tempListenerRefs = new ArrayList<>();
2083
2084                    tempListenerRefs.addAll(listenerRefs);
2085
2086                    if (tempListenerRefs.size() > 0) {
2087                        ArrayList<String> listeners = new ArrayList<>();
2088                        for (int i = 0; i < tempListenerRefs.size(); i++) {
2089                            if (!listeners.contains(tempListenerRefs.get(i))) {
2090                                listeners.add(tempListenerRefs.get(i));
2091                            }
2092                        }
2093
2094                        message.append("<br>");
2095                        message.append(Bundle.getMessage("ReminderInUse", count));
2096                        message.append("<ul>");
2097                        for (int i = 0; i < listeners.size(); i++) {
2098                            message.append("<li>");
2099                            message.append(listeners.get(i));
2100                            message.append("</li>");
2101                        }
2102                        message.append("</ul>");
2103
2104                        JEditorPane pane = new JEditorPane();
2105                        pane.setContentType("text/html");
2106                        pane.setText("<html>" + message.toString() + "</html>");
2107                        pane.setEditable(false);
2108                        JScrollPane jScrollPane = new JScrollPane(pane);
2109                        container.add(jScrollPane);
2110                    }
2111                } else {
2112                    String prompt = _maleSocket.getChildCount() > 0 ? "DeleteWithChildrenPrompt" : "DeletePrompt";
2113                    String msg = MessageFormat.format(Bundle.getMessage(prompt),
2114                            new Object[]{_maleSocket.getSystemName()});
2115                    JLabel question = new JLabel(msg);
2116                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
2117                    container.add(question);
2118                }
2119
2120                final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
2121                remember.setFont(remember.getFont().deriveFont(10f));
2122                remember.setAlignmentX(Component.CENTER_ALIGNMENT);
2123
2124                JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
2125                JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
2126                JPanel button = new JPanel();
2127                button.setAlignmentX(Component.CENTER_ALIGNMENT);
2128                button.add(yesButton);
2129                button.add(noButton);
2130                container.add(button);
2131
2132                noButton.addActionListener((ActionEvent e) -> {
2133                    //there is no point in remembering this the user will never be
2134                    //able to delete a bean!
2135                    dialog.dispose();
2136                });
2137
2138                yesButton.addActionListener((ActionEvent e) -> {
2139                    if (remember.isSelected()) {
2140                        setDisplayDeleteMsg(0x02);
2141                    }
2142                    doDelete();
2143                    dialog.dispose();
2144                });
2145                container.add(remember);
2146                container.setAlignmentX(Component.CENTER_ALIGNMENT);
2147                container.setAlignmentY(Component.CENTER_ALIGNMENT);
2148                dialog.getContentPane().add(container);
2149                dialog.pack();
2150                dialog.setLocation(
2151                        (Toolkit.getDefaultToolkit().getScreenSize().width) / 2 - dialog.getWidth() / 2,
2152                        (Toolkit.getDefaultToolkit().getScreenSize().height) / 2 - dialog.getHeight() / 2);
2153                dialog.setModal(true);
2154                dialog.setVisible(true);
2155            }
2156            return null;
2157        }
2158
2159        /**
2160         * {@inheritDoc} Minimal implementation to catch and log errors
2161         */
2162        @Override
2163        protected void done() {
2164            try {
2165                get();  // called to get errors
2166            } catch (InterruptedException | java.util.concurrent.ExecutionException e) {
2167                log.error("Exception while deleting bean", e);
2168            }
2169            _treePane.updateTree(_currentFemaleSocket, _currentPath.getPath());
2170        }
2171    }
2172
2173
2174
2175    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TreeEditor.class);
2176
2177}