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("CancelLogixButtonHint"));      // NOI18N
417        cancel.setToolTipText("CancelLogixButtonHint");      // NOI18N
418
419        _renameSocketDialog.addWindowListener(new java.awt.event.WindowAdapter() {
420            @Override
421            public void windowClosing(java.awt.event.WindowEvent e) {
422                cancelRenameSocketPressed(null);
423            }
424        });
425
426        _create = new JButton(Bundle.getMessage("ButtonOK"));  // NOI18N
427        panel5.add(_create);
428        _create.addActionListener((ActionEvent e) -> {
429            if (femaleSocket.validateName(_socketNameTextField.getText())) {
430                femaleSocket.setName(_socketNameTextField.getText());
431                cancelRenameSocketPressed(null);
432                for (TreeModelListener l : _treePane.femaleSocketTreeModel.listeners) {
433                    TreeModelEvent tme = new TreeModelEvent(
434                            femaleSocket,
435                            path.getPath()
436                    );
437                    l.treeNodesChanged(tme);
438                }
439                _treePane._tree.updateUI();
440                setPopupMenuLock(false);
441            } else {
442                JmriJOptionPane.showMessageDialog(null,
443                        Bundle.getMessage("ValidateFemaleSocketMessage", _socketNameTextField.getText()),
444                        Bundle.getMessage("ValidateFemaleSocketTitle"),
445                        JmriJOptionPane.ERROR_MESSAGE);
446            }
447        });
448
449        contentPanel.add(panel5);
450
451//        _renameSocketDialog.setLocationRelativeTo(component);
452        _renameSocketDialog.setLocationRelativeTo(null);
453        _renameSocketDialog.pack();
454        _renameSocketDialog.setVisible(true);
455    }
456
457    /**
458     * Respond to the Add menu choice in the popup menu.
459     *
460     * @param femaleSocket the female socket
461     * @param swingConfiguratorInterface the swing configurator used to configure the new class
462     * @param path the path to the item the user has clicked on
463     */
464    final protected void createAddFrame(FemaleSocket femaleSocket, TreePath path,
465            SwingConfiguratorInterface swingConfiguratorInterface) {
466        // possible change
467        _showReminder = true;
468        // make an Add Item Frame
469        if (_addItemDialog == null) {
470            MutableObject<String> commentStr = new MutableObject<>();
471            _addSwingConfiguratorInterface = swingConfiguratorInterface;
472            // Create item
473            _create = new JButton(Bundle.getMessage("ButtonCreate"));  // NOI18N
474            _create.addActionListener((ActionEvent e) -> {
475                _treePane._femaleRootSocket.unregisterListeners();
476
477                runOnConditionalNGThreadOrGUIThreadEventually(
478                        _treePane._femaleRootSocket.getConditionalNG(),
479                        () -> {
480
481                    List<String> errorMessages = new ArrayList<>();
482
483                    boolean isValid = true;
484
485                    if (!_prefs.getShowSystemUserNames()
486                            || (_systemName.getText().isEmpty() && _autoSystemName.isSelected())) {
487                        _systemName.setText(_addSwingConfiguratorInterface.getAutoSystemName());
488                    }
489
490                    if (_addSwingConfiguratorInterface.getManager()
491                            .validSystemNameFormat(_systemName.getText()) != Manager.NameValidity.VALID) {
492                        isValid = false;
493                        errorMessages.add(Bundle.getMessage("InvalidSystemName", _systemName.getText()));
494                    }
495
496                    isValid &= _addSwingConfiguratorInterface.validate(errorMessages);
497
498                    if (isValid) {
499                        MaleSocket socket;
500                        if (_addUserName.getText().isEmpty()) {
501                            socket = _addSwingConfiguratorInterface.createNewObject(_systemName.getText(), null);
502                        } else {
503                            socket = _addSwingConfiguratorInterface.createNewObject(_systemName.getText(), _addUserName.getText());
504                        }
505                        _addSwingConfiguratorInterfaceMaleSocket.updateObject(socket);
506    //                    for (Map.Entry<SwingConfiguratorInterface, Base> entry : _swingConfiguratorInterfaceList) {
507    //                        entry.getKey().updateObject(entry.getValue());
508    //                    }
509                        socket.setComment(commentStr.getValue());
510                        try {
511                            femaleSocket.connect(socket);
512                        } catch (SocketAlreadyConnectedException ex) {
513                            throw new RuntimeException(ex);
514                        }
515
516                        femaleSocket.forEntireTree((Base b) -> {
517                            b.addPropertyChangeListener(_treePane);
518                        });
519
520                        ThreadingUtil.runOnGUIEventually(() -> {
521                            _addSwingConfiguratorInterface.dispose();
522                            _addItemDialog.dispose();
523                            _addItemDialog = null;
524
525                            for (TreeModelListener l : _treePane.femaleSocketTreeModel.listeners) {
526                                TreeModelEvent tme = new TreeModelEvent(
527                                        femaleSocket,
528                                        path.getPath()
529                                );
530                                l.treeNodesChanged(tme);
531                            }
532                            _treePane._tree.expandPath(path);
533                            _treePane._tree.updateUI();
534
535                            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
536                                prefMgr.setCheckboxPreferenceState(_systemNameAuto, _autoSystemName.isSelected());
537                            });
538                        });
539                        setPopupMenuLock(false);
540                    } else {
541                        StringBuilder errorMsg = new StringBuilder();
542                        for (String s : errorMessages) {
543                            if (errorMsg.length() > 0) errorMsg.append("<br>");
544                            errorMsg.append(s);
545                        }
546                        JmriJOptionPane.showMessageDialog(null,
547                                Bundle.getMessage("ValidateErrorMessage", errorMsg),
548                                Bundle.getMessage("ValidateErrorTitle"),
549                                JmriJOptionPane.ERROR_MESSAGE);
550                    }
551                    ThreadingUtil.runOnGUIEventually(() -> {
552                        if (_treePane._femaleRootSocket.isActive()) {
553                            _treePane._femaleRootSocket.registerListeners();
554                        }
555                    });
556                });
557            });
558            _create.setToolTipText(Bundle.getMessage("CreateButtonHint"));  // NOI18N
559
560            if (_addSwingConfiguratorInterface != null) {
561                makeAddEditFrame(true, femaleSocket, _create, commentStr);
562            }
563        }
564    }
565
566    /**
567     * Respond to the Edit menu choice in the popup menu.
568     *
569     * @param femaleSocket the female socket
570     * @param path the path to the item the user has clicked on
571     */
572    final protected void editPressed(FemaleSocket femaleSocket, TreePath path) {
573        setPopupMenuLock(true);
574
575        // possible change
576        _showReminder = true;
577        // make an Edit Frame
578        if (_editActionExpressionDialog == null) {
579            Base object = femaleSocket.getConnectedSocket().getObject();
580            MutableObject<String> commentStr = new MutableObject<>(object.getComment());
581
582            // Edit ConditionalNG
583            _edit = new JButton(Bundle.getMessage("ButtonOK"));  // NOI18N
584            _edit.addActionListener((ActionEvent e) -> {
585
586                runOnConditionalNGThreadOrGUIThreadEventually(
587                        _treePane._femaleRootSocket.getConditionalNG(),
588                        () -> {
589
590                    List<String> errorMessages = new ArrayList<>();
591
592                    boolean isValid = true;
593
594                    if (_editSwingConfiguratorInterface.getManager() != null) {
595                        if (_editSwingConfiguratorInterface.getManager()
596                                .validSystemNameFormat(_systemName.getText()) != Manager.NameValidity.VALID) {
597                            isValid = false;
598                            errorMessages.add(Bundle.getMessage("InvalidSystemName", _systemName.getText()));
599                        }
600                    } else {
601                        log.debug("_editSwingConfiguratorInterface.getManager() returns null");
602                    }
603
604                    isValid &= _editSwingConfiguratorInterface.validate(errorMessages);
605
606                    boolean canClose = true;
607                    for (Map.Entry<SwingConfiguratorInterface, Base> entry : _swingConfiguratorInterfaceList) {
608                        if (!entry.getKey().canClose()) {
609                            canClose = false;
610                            break;
611                        }
612                    }
613
614                    if (isValid && canClose) {
615                        ThreadingUtil.runOnGUIEventually(() -> {
616                            femaleSocket.unregisterListeners();
617
618//                            Base object = femaleSocket.getConnectedSocket().getObject();
619                            if (_addUserName.getText().isEmpty()) {
620                                ((NamedBean)object).setUserName(null);
621                            } else {
622                                ((NamedBean)object).setUserName(_addUserName.getText());
623                            }
624                            ((NamedBean)object).setComment(commentStr.getValue());
625                            for (Map.Entry<SwingConfiguratorInterface, Base> entry : _swingConfiguratorInterfaceList) {
626                                entry.getKey().updateObject(entry.getValue());
627                                entry.getKey().dispose();
628                            }
629                            for (TreeModelListener l : _treePane.femaleSocketTreeModel.listeners) {
630                                TreeModelEvent tme = new TreeModelEvent(
631                                        femaleSocket,
632                                        path.getPath()
633                                );
634                                l.treeNodesChanged(tme);
635                            }
636                            _editActionExpressionDialog.dispose();
637                            _editActionExpressionDialog = null;
638                            _treePane._tree.updateUI();
639
640//                            if (femaleSocket.isActive()) femaleSocket.registerListeners();
641                            if (_treePane._femaleRootSocket.isActive()) {
642                                _treePane._femaleRootSocket.registerListeners();
643                            }
644                        });
645                        setPopupMenuLock(false);
646                    } else if (!isValid) {
647                        StringBuilder errorMsg = new StringBuilder();
648                        for (String s : errorMessages) {
649                            if (errorMsg.length() > 0) errorMsg.append("<br>");
650                            errorMsg.append(s);
651                        }
652                        ThreadingUtil.runOnGUIEventually(() -> {
653                            JmriJOptionPane.showMessageDialog(null,
654                                    Bundle.getMessage("ValidateErrorMessage", errorMsg),
655                                    Bundle.getMessage("ValidateErrorTitle"),
656                                    JmriJOptionPane.ERROR_MESSAGE);
657                        });
658                    }
659                });
660            });
661            _edit.setToolTipText(Bundle.getMessage("EditButtonHint"));  // NOI18N
662
663            makeAddEditFrame(false, femaleSocket, _edit, commentStr);
664        }
665    }
666
667    /**
668     * Create or edit action/expression dialog.
669     *
670     * @param addOrEdit true if add, false if edit
671     * @param femaleSocket the female socket to which we want to add something
672     * @param button a button to add to the dialog
673     * @param commentStr the new comment
674     */
675    final protected void makeAddEditFrame(
676            boolean addOrEdit,
677            FemaleSocket femaleSocket,
678            JButton button,
679            MutableObject<String> commentStr) {
680
681        JDialog dialog  = new JDialog(
682                this,
683                Bundle.getMessage(
684                        addOrEdit ? "AddMaleSocketDialogTitle" : "EditMaleSocketDialogTitle",
685                        femaleSocket.getLongDescription()),
686                false);
687//        frame.addHelpMenu(
688//                "package.jmri.jmrit.logixng.tools.swing.ConditionalNGAddEdit", true);     // NOI18N
689        Container contentPanel = dialog.getContentPane();
690        contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
691
692        JPanel p;
693        p = new JPanel();
694//        p.setLayout(new FlowLayout());
695        p.setLayout(new java.awt.GridBagLayout());
696        java.awt.GridBagConstraints c = new java.awt.GridBagConstraints();
697        c.gridwidth = 1;
698        c.gridheight = 1;
699        if (_prefs.getShowSystemUserNames()) {
700            c.gridx = 0;
701            c.gridy = 0;
702            c.anchor = java.awt.GridBagConstraints.EAST;
703            p.add(_sysNameLabel, c);
704            c.gridy = 1;
705            p.add(_userNameLabel, c);
706            c.gridy = 2;
707            c.gridx = 1;
708            c.gridy = 0;
709            c.anchor = java.awt.GridBagConstraints.WEST;
710            c.weightx = 1.0;
711            c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
712            p.add(_systemName, c);
713            c.gridy = 1;
714            p.add(_addUserName, c);
715            if (!femaleSocket.isConnected()) {
716                c.gridx = 2;
717                c.gridy = 1;
718                c.anchor = java.awt.GridBagConstraints.WEST;
719                c.weightx = 1.0;
720                c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
721                c.gridy = 0;
722                p.add(_autoSystemName, c);
723            }
724
725            if (addOrEdit) {
726                _systemName.setToolTipText(Bundle.getMessage("SystemNameHint",
727                        _addSwingConfiguratorInterface.getExampleSystemName()));
728                _addUserName.setToolTipText(Bundle.getMessage("UserNameHint"));
729            }
730        } else {
731            c.gridx = 0;
732            c.gridy = 0;
733        }
734        contentPanel.add(p);
735
736        if (femaleSocket.isConnected()) {
737            _systemName.setText(femaleSocket.getConnectedSocket().getSystemName());
738            _systemName.setEnabled(false);
739            _addUserName.setText(femaleSocket.getConnectedSocket().getUserName());
740        } else {
741            _systemName.setText("");
742            _systemName.setEnabled(true);
743            _addUserName.setText("");
744        }
745
746        // set up message
747        JPanel panel3 = new JPanel();
748        panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS));
749
750        // set up create and cancel buttons
751        JPanel panel5 = new JPanel();
752        panel5.setLayout(new FlowLayout());
753
754        Base object = null;
755
756        // Get panel for the item
757        _swingConfiguratorInterfaceList.clear();
758        List<JPanel> panels = new ArrayList<>();
759        if (femaleSocket.isConnected()) {
760            object = femaleSocket.getConnectedSocket();
761            while (object instanceof MaleSocket) {
762                SwingConfiguratorInterface swi =
763                        SwingTools.getSwingConfiguratorForClass(object.getClass());
764                panels.add(swi.getConfigPanel(object, panel5));
765                _swingConfiguratorInterfaceList.add(new HashMap.SimpleEntry<>(swi, object));
766                object = ((MaleSocket)object).getObject();
767            }
768            if (object != null) {
769                _editSwingConfiguratorInterface =
770                        SwingTools.getSwingConfiguratorForClass(object.getClass());
771                _editSwingConfiguratorInterface.setJDialog(dialog);
772                panels.add(_editSwingConfiguratorInterface.getConfigPanel(object, panel5));
773                _swingConfiguratorInterfaceList.add(new HashMap.SimpleEntry<>(_editSwingConfiguratorInterface, object));
774
775                dialog.setTitle(Bundle.getMessage(
776                        addOrEdit ? "AddMaleSocketDialogTitleWithType" : "EditMaleSocketDialogTitleWithType",
777                        femaleSocket.getLongDescription(),
778                        _editSwingConfiguratorInterface.toString())
779                );
780            } else {
781                // 'object' should be an action or expression but is null
782                JPanel panel = new JPanel();
783                panel.add(new JLabel("Error: femaleSocket.getConnectedSocket().getObject().getObject()....getObject() doesn't return a non MaleSocket"));
784                panels.add(panel);
785                log.error("femaleSocket.getConnectedSocket().getObject().getObject()....getObject() doesn't return a non MaleSocket");
786            }
787        } else {
788            Class<? extends MaleSocket> maleSocketClass =
789                    _addSwingConfiguratorInterface.getManager().getMaleSocketClass();
790            _addSwingConfiguratorInterfaceMaleSocket =
791                    SwingTools.getSwingConfiguratorForClass(maleSocketClass);
792
793            _addSwingConfiguratorInterfaceMaleSocket.setJDialog(dialog);
794            panels.add(_addSwingConfiguratorInterfaceMaleSocket.getConfigPanel(panel5));
795
796            _addSwingConfiguratorInterface.setJDialog(dialog);
797            panels.add(_addSwingConfiguratorInterface.getConfigPanel(panel5));
798
799            dialog.setTitle(Bundle.getMessage(
800                    addOrEdit ? "AddMaleSocketDialogTitleWithType" : "EditMaleSocketDialogTitleWithType",
801                    femaleSocket.getLongDescription(),
802                    _addSwingConfiguratorInterface.toString())
803            );
804        }
805        JPanel panel34 = new JPanel();
806        panel34.setLayout(new BoxLayout(panel34, BoxLayout.Y_AXIS));
807        for (int i = panels.size()-1; i >= 0; i--) {
808            JPanel panel = panels.get(i);
809            if (panel.getComponentCount() > 0) {
810                panel34.add(Box.createVerticalStrut(30));
811                panel34.add(panel);
812            }
813        }
814        panel3.add(panel34);
815        contentPanel.add(panel3);
816
817        // Edit comment
818        JButton editComment = new JButton(Bundle.getMessage("ButtonEditComment"));    // NOI18N
819        panel5.add(editComment);
820        String comment = object != null ? object.getComment() : "";
821        editComment.addActionListener((ActionEvent e) -> {
822            commentStr.setValue(new EditCommentDialog().showDialog(comment));
823        });
824
825        // Function help
826        JButton showFunctionHelp = new JButton(Bundle.getMessage("ButtonFunctionHelp"));    // NOI18N
827        panel5.add(showFunctionHelp);
828        showFunctionHelp.addActionListener((ActionEvent e) -> {
829            InstanceManager.getDefault(FunctionsHelpDialog.class).showDialog();
830        });
831//        showFunctionHelp.setToolTipText("FunctionHelpButtonHint");      // NOI18N
832
833        // Cancel
834        JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));    // NOI18N
835        panel5.add(cancel);
836        cancel.addActionListener((ActionEvent e) -> {
837            if (!femaleSocket.isConnected()) {
838                cancelCreateItem(null);
839            } else {
840                cancelEditPressed(null);
841            }
842        });
843//        cancel.setToolTipText(Bundle.getMessage("CancelLogixButtonHint"));      // NOI18N
844        cancel.setToolTipText("CancelLogixButtonHint");      // NOI18N
845
846        panel5.add(button);
847
848        dialog.addWindowListener(new java.awt.event.WindowAdapter() {
849            @Override
850            public void windowClosing(java.awt.event.WindowEvent e) {
851                if (addOrEdit) {
852                    cancelCreateItem(null);
853                } else {
854                    cancelEditPressed(null);
855                }
856            }
857        });
858
859        contentPanel.add(panel5);
860
861        _autoSystemName.addItemListener((ItemEvent e) -> {
862            autoSystemName();
863        });
864//        addLogixNGFrame.setLocationRelativeTo(component);
865        dialog.pack();
866        dialog.setLocationRelativeTo(null);
867
868        dialog.getRootPane().setDefaultButton(button);
869
870        if (addOrEdit) {
871            _addItemDialog = dialog;
872        } else {
873            _editActionExpressionDialog = dialog;
874        }
875
876        _autoSystemName.setSelected(true);
877        InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> {
878            _autoSystemName.setSelected(prefMgr.getCheckboxPreferenceState(_systemNameAuto, true));
879        });
880
881        dialog.setVisible(true);
882    }
883
884    /**
885     * Respond to the Local Variables menu choice in the popup menu.
886     *
887     * @param femaleSocket the female socket
888     * @param path the path to the item the user has clicked on
889     */
890    final protected void editLocalVariables(FemaleSocket femaleSocket, TreePath path) {
891        // possible change
892        _showReminder = true;
893        setPopupMenuLock(true);
894        // make an Edit Frame
895        if (_editLocalVariablesDialog == null) {
896            MaleSocket maleSocket = femaleSocket.getConnectedSocket();
897
898            // Edit ConditionalNG
899            _edit = new JButton(Bundle.getMessage("ButtonOK"));  // NOI18N
900            _edit.addActionListener((ActionEvent e) -> {
901                List<String> errorMessages = new ArrayList<>();
902                boolean hasErrors = false;
903                for (SymbolTable.VariableData v : _localVariableTableModel.getVariables()) {
904                    if (v.getName().isEmpty()) {
905                        errorMessages.add(Bundle.getMessage("VariableNameIsEmpty", v.getName()));
906                        hasErrors = true;
907                    }
908                    if (! SymbolTable.validateName(v.getName())) {
909                        errorMessages.add(Bundle.getMessage("VariableNameIsNotValid", v.getName()));
910                        hasErrors = true;
911                    }
912                }
913
914                if (hasErrors) {
915                    StringBuilder errorMsg = new StringBuilder();
916                    for (String s : errorMessages) {
917                        if (errorMsg.length() > 0) errorMsg.append("<br>");
918                        errorMsg.append(s);
919                    }
920                    JmriJOptionPane.showMessageDialog(null,
921                            Bundle.getMessage("ValidateErrorMessage", errorMsg),
922                            Bundle.getMessage("ValidateErrorTitle"),
923                            JmriJOptionPane.ERROR_MESSAGE);
924
925                } else {
926                    _treePane._femaleRootSocket.unregisterListeners();
927
928                    runOnConditionalNGThreadOrGUIThreadEventually(
929                            _treePane._femaleRootSocket.getConditionalNG(),
930                            () -> {
931
932                        maleSocket.clearLocalVariables();
933                        for (SymbolTable.VariableData variableData : _localVariableTableModel.getVariables()) {
934                            maleSocket.addLocalVariable(variableData);
935                        }
936
937                        ThreadingUtil.runOnGUIEventually(() -> {
938                            _editLocalVariablesDialog.dispose();
939                            _editLocalVariablesDialog = null;
940                            if (_treePane._femaleRootSocket.isActive()) {
941                                _treePane._femaleRootSocket.registerListeners();
942                            }
943                            for (TreeModelListener l : _treePane.femaleSocketTreeModel.listeners) {
944                                TreeModelEvent tme = new TreeModelEvent(
945                                        femaleSocket,
946                                        path.getPath()
947                                );
948                                l.treeNodesChanged(tme);
949                            }
950                            _treePane._tree.updateUI();
951                        });
952                        setPopupMenuLock(false);
953                    });
954                }
955            });
956//            _edit.setToolTipText(Bundle.getMessage("EditButtonHint"));  // NOI18N
957
958//            makeAddEditFrame(false, femaleSocket, _editSwingConfiguratorInterface, _edit);  // NOI18N
959
960            _editLocalVariablesDialog = new JDialog(
961                    this,
962                    Bundle.getMessage(
963                            "EditLocalVariablesDialogTitle",
964                            femaleSocket.getLongDescription()),
965                    false);
966    //        frame.addHelpMenu(
967    //                "package.jmri.jmrit.logixng.tools.swing.ConditionalNGAddEdit", true);     // NOI18N
968            Container contentPanel = _editLocalVariablesDialog.getContentPane();
969            contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
970
971            JTable table = new JTable();
972            _localVariableTableModel = new LocalVariableTableModel(maleSocket);
973            table.setModel(_localVariableTableModel);
974            table.setDefaultRenderer(InitialValueType.class,
975                    new LocalVariableTableModel.TypeCellRenderer());
976            table.setDefaultEditor(InitialValueType.class,
977                    new LocalVariableTableModel.TypeCellEditor());
978            table.setDefaultRenderer(LocalVariableTableModel.Menu.class,
979                    new LocalVariableTableModel.MenuCellRenderer());
980            table.setDefaultEditor(LocalVariableTableModel.Menu.class,
981                    new LocalVariableTableModel.MenuCellEditor(table, _localVariableTableModel));
982            _localVariableTableModel.setColumnForMenu(table);
983            JScrollPane scrollpane = new JScrollPane(table);
984            scrollpane.setPreferredSize(new Dimension(400, 200));
985            contentPanel.add(scrollpane);
986
987            // set up create and cancel buttons
988            JPanel buttonPanel = new JPanel();
989            buttonPanel.setLayout(new FlowLayout());
990
991            // Function help
992            JButton showFunctionHelp = new JButton(Bundle.getMessage("ButtonFunctionHelp"));    // NOI18N
993            buttonPanel.add(showFunctionHelp);
994            showFunctionHelp.addActionListener((ActionEvent e) -> {
995                InstanceManager.getDefault(FunctionsHelpDialog.class).showDialog();
996            });
997//            showFunctionHelp.setToolTipText("FunctionHelpButtonHint");      // NOI18N
998
999            // Add local variable
1000            JButton add = new JButton(Bundle.getMessage("TableAddVariable"));
1001            buttonPanel.add(add);
1002            add.addActionListener((ActionEvent e) -> {
1003                _localVariableTableModel.add();
1004            });
1005
1006            // Cancel
1007            JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));    // NOI18N
1008            buttonPanel.add(cancel);
1009            cancel.addActionListener((ActionEvent e) -> {
1010                _editLocalVariablesDialog.setVisible(false);
1011                _editLocalVariablesDialog.dispose();
1012                _editLocalVariablesDialog = null;
1013                setPopupMenuLock(false);
1014            });
1015    //        cancel.setToolTipText(Bundle.getMessage("CancelLogixButtonHint"));      // NOI18N
1016            cancel.setToolTipText("CancelLogixButtonHint");      // NOI18N
1017
1018            buttonPanel.add(_edit);
1019            _editLocalVariablesDialog.getRootPane().setDefaultButton(_edit);
1020
1021            _editLocalVariablesDialog.addWindowListener(new java.awt.event.WindowAdapter() {
1022                @Override
1023                public void windowClosing(java.awt.event.WindowEvent e) {
1024                    _editLocalVariablesDialog.setVisible(false);
1025                    _editLocalVariablesDialog.dispose();
1026                    _editLocalVariablesDialog = null;
1027                    setPopupMenuLock(false);
1028                }
1029            });
1030
1031            contentPanel.add(buttonPanel);
1032
1033            _autoSystemName.addItemListener((ItemEvent e) -> {
1034                autoSystemName();
1035            });
1036    //        addLogixNGFrame.setLocationRelativeTo(component);
1037            _editLocalVariablesDialog.pack();
1038            _editLocalVariablesDialog.setLocationRelativeTo(null);
1039
1040            _editLocalVariablesDialog.setVisible(true);
1041        }
1042    }
1043
1044    /**
1045     * Respond to the Change user name menu choice in the popup menu.
1046     *
1047     * @param femaleSocket the female socket
1048     * @param path the path to the item the user has clicked on
1049     */
1050    final protected void changeUsername(FemaleSocket femaleSocket, TreePath path) {
1051        // possible change
1052        _showReminder = true;
1053        setPopupMenuLock(true);
1054        // make an Edit Frame
1055        if (_changeUsernameDialog == null) {
1056            MaleSocket maleSocket = femaleSocket.getConnectedSocket();
1057
1058            // Edit ConditionalNG
1059            _edit = new JButton(Bundle.getMessage("ButtonOK"));  // NOI18N
1060            _edit.addActionListener((ActionEvent e) -> {
1061
1062                boolean hasErrors = false;
1063                if (hasErrors) {
1064                    String errorMsg = "";
1065                    JmriJOptionPane.showMessageDialog(null,
1066                            Bundle.getMessage("ValidateErrorMessage", errorMsg),
1067                            Bundle.getMessage("ValidateErrorTitle"),
1068                            JmriJOptionPane.ERROR_MESSAGE);
1069
1070                } else {
1071                    _treePane._femaleRootSocket.unregisterListeners();
1072
1073                    runOnConditionalNGThreadOrGUIThreadEventually(
1074                            _treePane._femaleRootSocket.getConditionalNG(),
1075                            () -> {
1076
1077                        String username = _usernameField.getText();
1078                        if (username.equals("")) username = null;
1079
1080                        // Only change user name if it's changed
1081                        if (((username == null) && (maleSocket.getUserName() != null))
1082                                || ((username != null) && !username.equals(maleSocket.getUserName()))) {
1083
1084                            if (username != null) {
1085                                NamedBean nB = maleSocket.getManager().getByUserName(username);
1086                                if (nB != null) {
1087                                    String uname = username;
1088                                    ThreadingUtil.runOnGUIEventually(() -> {
1089                                        log.error("User name is not unique {}", uname);
1090                                        String msg = Bundle.getMessage("WarningUserName", new Object[]{("" + uname)});
1091                                        JmriJOptionPane.showMessageDialog(null, msg,
1092                                                Bundle.getMessage("WarningTitle"),
1093                                                JmriJOptionPane.ERROR_MESSAGE);
1094                                    });
1095                                    username = null;
1096                                }
1097                            }
1098
1099                            maleSocket.setUserName(username);
1100
1101                            MaleSocket m = maleSocket;
1102                            while (! (m instanceof NamedBean)) m = (MaleSocket) m.getObject();
1103
1104                            NamedBeanHandleManager nbMan = InstanceManager.getDefault(NamedBeanHandleManager.class);
1105                            if (nbMan.inUse(maleSocket.getSystemName(), (NamedBean)m)) {
1106                                String msg = Bundle.getMessage("UpdateToUserName", new Object[]{maleSocket.getManager().getBeanTypeHandled(), username, maleSocket.getSystemName()});
1107                                int optionPane = JmriJOptionPane.showConfirmDialog(null,
1108                                        msg, Bundle.getMessage("UpdateToUserNameTitle"),
1109                                        JmriJOptionPane.YES_NO_OPTION);
1110                                if (optionPane == JmriJOptionPane.YES_OPTION) {
1111                                    //This will update the bean reference from the systemName to the userName
1112                                    try {
1113                                        nbMan.updateBeanFromSystemToUser((NamedBean)m);
1114                                    } catch (JmriException ex) {
1115                                        //We should never get an exception here as we already check that the username is not valid
1116                                        log.error("Impossible exception setting user name", ex);
1117                                    }
1118                                }
1119                            }
1120                        }
1121
1122                        ThreadingUtil.runOnGUIEventually(() -> {
1123                            if (_treePane._femaleRootSocket.isActive()) {
1124                                _treePane._femaleRootSocket.registerListeners();
1125                            }
1126                            _changeUsernameDialog.dispose();
1127                            _changeUsernameDialog = null;
1128                            for (TreeModelListener l : _treePane.femaleSocketTreeModel.listeners) {
1129                                TreeModelEvent tme = new TreeModelEvent(
1130                                        femaleSocket,
1131                                        path.getPath()
1132                                );
1133                                l.treeNodesChanged(tme);
1134                            }
1135                            _treePane._tree.updateUI();
1136                        });
1137                        setPopupMenuLock(false);
1138                    });
1139                }
1140            });
1141//            _edit.setToolTipText(Bundle.getMessage("EditButtonHint"));  // NOI18N
1142
1143//            makeAddEditFrame(false, femaleSocket, _editSwingConfiguratorInterface, _edit);  // NOI18N
1144
1145            _changeUsernameDialog = new JDialog(
1146                    this,
1147                    Bundle.getMessage(
1148                            "EditLocalVariablesDialogTitle",
1149                            femaleSocket.getLongDescription()),
1150                    false);
1151    //        frame.addHelpMenu(
1152    //                "package.jmri.jmrit.logixng.tools.swing.ConditionalNGAddEdit", true);     // NOI18N
1153            Container contentPanel = _changeUsernameDialog.getContentPane();
1154            contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
1155
1156//            JPanel tablePanel = new JPanel();
1157
1158            JLabel usernameLabel = new JLabel("Username");
1159            _usernameField.setText(maleSocket.getUserName());
1160
1161            contentPanel.add(usernameLabel);
1162            contentPanel.add(_usernameField);
1163
1164            // set up create and cancel buttons
1165            JPanel buttonPanel = new JPanel();
1166            buttonPanel.setLayout(new FlowLayout());
1167
1168            // Cancel
1169            JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));    // NOI18N
1170            buttonPanel.add(cancel);
1171            cancel.addActionListener((ActionEvent e) -> {
1172                _changeUsernameDialog.setVisible(false);
1173                _changeUsernameDialog.dispose();
1174                _changeUsernameDialog = null;
1175                setPopupMenuLock(false);
1176            });
1177    //        cancel.setToolTipText(Bundle.getMessage("CancelLogixButtonHint"));      // NOI18N
1178            cancel.setToolTipText("CancelLogixButtonHint");      // NOI18N
1179
1180            buttonPanel.add(_edit);
1181            _changeUsernameDialog.getRootPane().setDefaultButton(_edit);
1182
1183            _changeUsernameDialog.addWindowListener(new java.awt.event.WindowAdapter() {
1184                @Override
1185                public void windowClosing(java.awt.event.WindowEvent e) {
1186                    _changeUsernameDialog.setVisible(false);
1187                    _changeUsernameDialog.dispose();
1188                    _changeUsernameDialog = null;
1189                    setPopupMenuLock(false);
1190                }
1191            });
1192
1193            contentPanel.add(buttonPanel);
1194
1195            _autoSystemName.addItemListener((ItemEvent e) -> {
1196                autoSystemName();
1197            });
1198    //        addLogixNGFrame.setLocationRelativeTo(component);
1199            _changeUsernameDialog.pack();
1200            _changeUsernameDialog.setLocationRelativeTo(null);
1201
1202            _changeUsernameDialog.setVisible(true);
1203        }
1204    }
1205
1206    /**
1207     * Enable/disable fields for data entry when user selects to have system
1208     * name automatically generated.
1209     */
1210    final protected void autoSystemName() {
1211        if (_autoSystemName.isSelected()) {
1212            _systemName.setEnabled(false);
1213            _sysNameLabel.setEnabled(false);
1214        } else {
1215            _systemName.setEnabled(true);
1216            _sysNameLabel.setEnabled(true);
1217        }
1218    }
1219
1220    /**
1221     * Respond to the Cancel button in Rename socket window.
1222     * <p>
1223     * Note: Also get there if the user closes the Rename socket window.
1224     *
1225     * @param e The event heard
1226     */
1227    final protected void cancelRenameSocketPressed(ActionEvent e) {
1228        _renameSocketDialog.setVisible(false);
1229        _renameSocketDialog.dispose();
1230        _renameSocketDialog = null;
1231        setPopupMenuLock(false);
1232        this.setVisible(true);
1233    }
1234
1235    /**
1236     * Respond to the Cancel button in Add ConditionalNG window.
1237     * <p>
1238     * Note: Also get there if the user closes the Add ConditionalNG window.
1239     *
1240     * @param e The event heard
1241     */
1242    final protected void cancelCreateItem(ActionEvent e) {
1243        _addItemDialog.setVisible(false);
1244        _addSwingConfiguratorInterface.dispose();
1245        _addItemDialog.dispose();
1246        _addItemDialog = null;
1247        setPopupMenuLock(false);
1248//        _inCopyMode = false;
1249        this.setVisible(true);
1250    }
1251
1252
1253    /**
1254     * Respond to the Cancel button in Add ConditionalNG window.
1255     * <p>
1256     * Note: Also get there if the user closes the Add ConditionalNG window.
1257     *
1258     * @param e The event heard
1259     */
1260    final protected void cancelEditPressed(ActionEvent e) {
1261        for (Map.Entry<SwingConfiguratorInterface, Base> entry : _swingConfiguratorInterfaceList) {
1262            // Abort if we cannot close the dialog
1263            if (!entry.getKey().canClose()) return;
1264        }
1265
1266        _editActionExpressionDialog.setVisible(false);
1267
1268        for (Map.Entry<SwingConfiguratorInterface, Base> entry : _swingConfiguratorInterfaceList) {
1269            entry.getKey().dispose();
1270        }
1271        _editActionExpressionDialog.dispose();
1272        _editActionExpressionDialog = null;
1273        setPopupMenuLock(false);
1274        this.setVisible(true);
1275    }
1276
1277
1278    protected void executeEvaluate(SwingConfiguratorInterface swi, MaleSocket maleSocket) {
1279        swi.executeEvaluate(maleSocket);
1280    }
1281
1282    private boolean itemIsSystem(FemaleSocket femaleSocket) {
1283        return (femaleSocket.isConnected())
1284                && femaleSocket.getConnectedSocket().isSystem();
1285    }
1286
1287    private boolean parentIsSystem(FemaleSocket femaleSocket) {
1288        Base parent = femaleSocket.getParent();
1289        while ((parent != null) && !(femaleSocket.getParent() instanceof MaleSocket)) {
1290            parent = parent.getParent();
1291        }
1292        return (parent != null) && ((MaleSocket)parent).isSystem();
1293    }
1294
1295    /**
1296     * Asks the user if edit a system node.
1297     * @return true if not edit system node, else return false
1298     */
1299    private boolean abortEditAboutSystem(Base b) {
1300        int result = JmriJOptionPane.showConfirmDialog(
1301                this,
1302                Bundle.getMessage("TreeEditor_ChangeSystemNode"),
1303                b.getLongDescription(),
1304                JmriJOptionPane.YES_NO_OPTION);
1305
1306        return ( result != JmriJOptionPane.YES_OPTION );
1307    }
1308
1309    private void editItem(FemaleSocket femaleSocket, TreePath path) {
1310        if (itemIsSystem(femaleSocket) && abortEditAboutSystem(femaleSocket.getConnectedSocket())) {
1311            return;
1312        }
1313        editPressed(femaleSocket, path);
1314    }
1315
1316    private void removeItem(FemaleSocket femaleSocket, TreePath path) {
1317        if ((parentIsSystem(femaleSocket) || itemIsSystem(femaleSocket)) && abortEditAboutSystem(femaleSocket.getConnectedSocket())) {
1318            return;
1319        }
1320        DeleteBeanWorker worker = new DeleteBeanWorker(femaleSocket, path);
1321        worker.execute();
1322    }
1323
1324    private void cutItem(FemaleSocket femaleSocket, TreePath path) {
1325        if ((parentIsSystem(femaleSocket) || itemIsSystem(femaleSocket)) && abortEditAboutSystem(femaleSocket.getConnectedSocket())) {
1326            return;
1327        }
1328
1329        if (femaleSocket.isConnected()) {
1330            _treePane._femaleRootSocket.unregisterListeners();
1331
1332            runOnConditionalNGThreadOrGUIThreadEventually(
1333                    _treePane._femaleRootSocket.getConditionalNG(),
1334                    () -> {
1335                Clipboard clipboard =
1336                        InstanceManager.getDefault(LogixNG_Manager.class).getClipboard();
1337                List<String> errors = new ArrayList<>();
1338                MaleSocket maleSocket = femaleSocket.getConnectedSocket();
1339                femaleSocket.disconnect();
1340                if (!clipboard.add(maleSocket, errors)) {
1341                    JmriJOptionPane.showMessageDialog(this,
1342                            String.join("<br>", errors),
1343                            Bundle.getMessage("TitleError"),
1344                            JmriJOptionPane.ERROR_MESSAGE);
1345                }
1346                ThreadingUtil.runOnGUIEventually(() -> {
1347                    maleSocket.forEntireTree((Base b) -> {
1348                        b.removePropertyChangeListener(_treePane);
1349                        if (_clipboardEditor != null) {
1350                            b.addPropertyChangeListener(_clipboardEditor._treePane);
1351                        }
1352                    });
1353                    _treePane._femaleRootSocket.registerListeners();
1354                    _treePane.updateTree(femaleSocket, path.getPath());
1355                });
1356            });
1357        } else {
1358            log.error("_currentFemaleSocket is not connected");
1359        }
1360    }
1361
1362    private void copyItem(FemaleSocket femaleSocket) {
1363        if ((parentIsSystem(femaleSocket) || itemIsSystem(femaleSocket)) && abortEditAboutSystem(femaleSocket.getConnectedSocket())) {
1364            return;
1365        }
1366
1367        if (femaleSocket.isConnected()) {
1368           _treePane._femaleRootSocket.unregisterListeners();
1369
1370           runOnConditionalNGThreadOrGUIThreadEventually(
1371                   _treePane._femaleRootSocket.getConditionalNG(),
1372                   () -> {
1373               Clipboard clipboard =
1374                       InstanceManager.getDefault(LogixNG_Manager.class).getClipboard();
1375               Map<String, String> systemNames = new HashMap<>();
1376               Map<String, String> userNames = new HashMap<>();
1377               MaleSocket maleSocket = null;
1378               try {
1379                   maleSocket = (MaleSocket) femaleSocket
1380                           .getConnectedSocket()
1381                           .getDeepCopy(systemNames, userNames);
1382                   List<String> errors = new ArrayList<>();
1383                   if (!clipboard.add(
1384                           maleSocket,
1385                           errors)) {
1386                       JmriJOptionPane.showMessageDialog(this,
1387                               String.join("<br>", errors),
1388                               Bundle.getMessage("TitleError"),
1389                               JmriJOptionPane.ERROR_MESSAGE);
1390                   }
1391               } catch (JmriException ex) {
1392                   log.error("getDeepCopy thrown exception: {}", ex, ex);
1393                   ThreadingUtil.runOnGUIEventually(() -> {
1394                       JmriJOptionPane.showMessageDialog(null,
1395                               "An exception has occured: "+ex.getMessage(),
1396                               "An error has occured",
1397                               JmriJOptionPane.ERROR_MESSAGE);
1398                   });
1399               }
1400               if (maleSocket != null) {
1401                   MaleSocket socket = maleSocket;
1402                   ThreadingUtil.runOnGUIEventually(() -> {
1403                       socket.forEntireTree((Base b) -> {
1404                           if (_clipboardEditor != null) {
1405                               b.addPropertyChangeListener(_clipboardEditor._treePane);
1406                           }
1407                       });
1408                   });
1409               }
1410           });
1411
1412           _treePane._femaleRootSocket.registerListeners();
1413       } else {
1414           log.error("_currentFemaleSocket is not connected");
1415       }
1416    }
1417
1418    private void pasteItem(FemaleSocket femaleSocket, TreePath path) {
1419        if (parentIsSystem(femaleSocket) && abortEditAboutSystem(femaleSocket.getParent())) {
1420            return;
1421        }
1422
1423        if (! femaleSocket.isConnected()) {
1424            _treePane._femaleRootSocket.unregisterListeners();
1425
1426            runOnConditionalNGThreadOrGUIThreadEventually(
1427                    _treePane._femaleRootSocket.getConditionalNG(),
1428                    () -> {
1429                Clipboard clipboard =
1430                        InstanceManager.getDefault(LogixNG_Manager.class).getClipboard();
1431                try {
1432                    if (clipboard.getTopItem() == null) {
1433                        return;
1434                    }
1435                    femaleSocket.connect(clipboard.fetchTopItem());
1436                    List<String> errors = new ArrayList<>();
1437                    if (!femaleSocket.setParentForAllChildren(errors)) {
1438                        JmriJOptionPane.showMessageDialog(this,
1439                                String.join("<br>", errors),
1440                                Bundle.getMessage("TitleError"),
1441                                JmriJOptionPane.ERROR_MESSAGE);
1442                    }
1443                } catch (SocketAlreadyConnectedException ex) {
1444                    log.error("item cannot be connected", ex);
1445                }
1446                ThreadingUtil.runOnGUIEventually(() -> {
1447                    _treePane._femaleRootSocket.forEntireTree((Base b) -> {
1448                        // Remove the listener if it is already
1449                        // added so we don't end up with duplicate
1450                        // listeners.
1451                        b.removePropertyChangeListener(_treePane);
1452                        b.addPropertyChangeListener(_treePane);
1453                    });
1454                    _treePane._femaleRootSocket.registerListeners();
1455                    _treePane.updateTree(femaleSocket, path.getPath());
1456                });
1457            });
1458        } else {
1459            log.error("_currentFemaleSocket is connected");
1460        }
1461    }
1462
1463    private void pasteCopy(FemaleSocket femaleSocket, TreePath path) {
1464        if (parentIsSystem(femaleSocket) && abortEditAboutSystem(femaleSocket.getParent())) {
1465            return;
1466        }
1467
1468        if (! femaleSocket.isConnected()) {
1469            _treePane._femaleRootSocket.unregisterListeners();
1470
1471            runOnConditionalNGThreadOrGUIThreadEventually(
1472                    _treePane._femaleRootSocket.getConditionalNG(),
1473                    () -> {
1474                Clipboard clipboard =
1475                        InstanceManager.getDefault(LogixNG_Manager.class).getClipboard();
1476
1477                Map<String, String> systemNames = new HashMap<>();
1478                Map<String, String> userNames = new HashMap<>();
1479                MaleSocket maleSocket = null;
1480                try {
1481                    maleSocket = (MaleSocket) clipboard.getTopItem()
1482                            .getDeepCopy(systemNames, userNames);
1483                } catch (JmriException ex) {
1484                    log.error("getDeepCopy thrown exception: {}", ex, ex);
1485                    ThreadingUtil.runOnGUIEventually(() -> {
1486                        JmriJOptionPane.showMessageDialog(null,
1487                                "An exception has occured: "+ex.getMessage(),
1488                                "An error has occured",
1489                                JmriJOptionPane.ERROR_MESSAGE);
1490                    });
1491                }
1492                if (maleSocket != null) {
1493                    try {
1494                        femaleSocket.connect(maleSocket);
1495                        List<String> errors = new ArrayList<>();
1496                        if (!femaleSocket.setParentForAllChildren(errors)) {
1497                            JmriJOptionPane.showMessageDialog(this,
1498                                    String.join("<br>", errors),
1499                                    Bundle.getMessage("TitleError"),
1500                                    JmriJOptionPane.ERROR_MESSAGE);
1501                        }
1502                    } catch (SocketAlreadyConnectedException ex) {
1503                        log.error("item cannot be connected", ex);
1504                    }
1505                    ThreadingUtil.runOnGUIEventually(() -> {
1506                        _treePane._femaleRootSocket.forEntireTree((Base b) -> {
1507                            // Remove the listener if it is already
1508                            // added so we don't end up with duplicate
1509                            // listeners.
1510                            b.removePropertyChangeListener(_treePane);
1511                            b.addPropertyChangeListener(_treePane);
1512                        });
1513                        _treePane._femaleRootSocket.registerListeners();
1514                        _treePane.updateTree(femaleSocket, path.getPath());
1515                    });
1516                }
1517            });
1518        } else {
1519            log.error("_currentFemaleSocket is connected");
1520        }
1521    }
1522
1523    private void doIt(String command, FemaleSocket femaleSocket, TreePath path) {
1524        Base parent = femaleSocket.getParent();
1525        while ((parent != null) && !(femaleSocket.getParent() instanceof MaleSocket)) {
1526            parent = parent.getParent();
1527        }
1528        boolean parentIsSystem = (parent != null) && ((MaleSocket)parent).isSystem();
1529        boolean itemIsSystem = itemIsSystem(femaleSocket);
1530
1531        switch (command) {
1532            case ACTION_COMMAND_RENAME_SOCKET:
1533                if (parentIsSystem && abortEditAboutSystem(femaleSocket.getParent())) break;
1534                renameSocketPressed(femaleSocket, path);
1535                break;
1536
1537            case ACTION_COMMAND_EDIT:
1538                editItem(femaleSocket, path);
1539                break;
1540
1541            case ACTION_COMMAND_REMOVE:
1542                removeItem(femaleSocket, path);
1543                break;
1544
1545            case ACTION_COMMAND_CUT:
1546                cutItem(femaleSocket, path);
1547                break;
1548
1549            case ACTION_COMMAND_COPY:
1550                copyItem(femaleSocket);
1551                break;
1552
1553            case ACTION_COMMAND_PASTE:
1554                pasteItem(femaleSocket, path);
1555                break;
1556
1557            case ACTION_COMMAND_PASTE_COPY:
1558                pasteCopy(femaleSocket, path);
1559                break;
1560
1561            case ACTION_COMMAND_ENABLE:
1562                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1563
1564                femaleSocket.getConnectedSocket().setEnabled(true);
1565                runOnConditionalNGThreadOrGUIThreadEventually(
1566                        _treePane._femaleRootSocket.getConditionalNG(),
1567                        () -> {
1568                    ThreadingUtil.runOnGUIEventually(() -> {
1569                        _treePane._femaleRootSocket.unregisterListeners();
1570                        _treePane.updateTree(femaleSocket, path.getPath());
1571                        _treePane._femaleRootSocket.registerListeners();
1572                    });
1573                });
1574                break;
1575
1576            case ACTION_COMMAND_DISABLE:
1577                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1578
1579                femaleSocket.getConnectedSocket().setEnabled(false);
1580                runOnConditionalNGThreadOrGUIThreadEventually(
1581                        _treePane._femaleRootSocket.getConditionalNG(),
1582                        () -> {
1583                    ThreadingUtil.runOnGUIEventually(() -> {
1584                        _treePane._femaleRootSocket.unregisterListeners();
1585                        _treePane.updateTree(femaleSocket, path.getPath());
1586                        _treePane._femaleRootSocket.registerListeners();
1587                    });
1588                });
1589                break;
1590
1591            case ACTION_COMMAND_LOCK:
1592                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1593
1594                femaleSocket.forEntireTree((item) -> {
1595                    if (item instanceof MaleSocket) {
1596                        ((MaleSocket)item).setLocked(true);
1597                    }
1598                });
1599                _treePane.updateTree(femaleSocket, path.getPath());
1600                break;
1601
1602            case ACTION_COMMAND_UNLOCK:
1603                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1604
1605                femaleSocket.forEntireTree((item) -> {
1606                    if (item instanceof MaleSocket) {
1607                        ((MaleSocket)item).setLocked(false);
1608                    }
1609                });
1610                _treePane.updateTree(femaleSocket, path.getPath());
1611                break;
1612
1613            case ACTION_COMMAND_LOCAL_VARIABLES:
1614                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1615                editLocalVariables(femaleSocket, path);
1616                break;
1617
1618            case ACTION_COMMAND_CHANGE_USERNAME:
1619                if (itemIsSystem && abortEditAboutSystem(femaleSocket.getConnectedSocket())) break;
1620                changeUsername(femaleSocket, path);
1621                break;
1622
1623            case ACTION_COMMAND_EXECUTE_EVALUATE:
1624                Base object = femaleSocket.getConnectedSocket();
1625                if (object == null) throw new NullPointerException("object is null");
1626                while (object instanceof MaleSocket) {
1627                    object = ((MaleSocket)object).getObject();
1628                }
1629                SwingConfiguratorInterface swi =
1630                        SwingTools.getSwingConfiguratorForClass(object.getClass());
1631                executeEvaluate(swi, femaleSocket.getConnectedSocket());
1632                break;
1633
1634/*
1635            case ACTION_COMMAND_EXPAND_TREE:
1636                // jtree expand sub tree
1637                // https://stackoverflow.com/questions/15210979/how-do-i-auto-expand-a-jtree-when-setting-a-new-treemodel
1638                // https://www.tutorialspoint.com/how-to-expand-jtree-row-to-display-all-the-nodes-and-child-nodes-in-java
1639                // To expand all rows, do this:
1640                for (int i = 0; i < tree.getRowCount(); i++) {
1641                    tree.expandRow(i);
1642                }
1643
1644                tree.expandPath(_currentPath);
1645                tree.updateUI();
1646                break;
1647*/
1648            default:
1649                // Check if the action is a female socket operation
1650                if (!checkFemaleSocketOperation(femaleSocket, parentIsSystem, itemIsSystem, command)) {
1651                    log.error("e.getActionCommand() returns unknown value {}", command);
1652                }
1653        }
1654    }
1655
1656    private boolean checkFemaleSocketOperation(
1657            FemaleSocket femaleSocket,
1658            boolean parentIsSystem,
1659            boolean itemIsSystem,
1660            String command) {
1661
1662        for (FemaleSocketOperation oper : FemaleSocketOperation.values()) {
1663            if (oper.name().equals(command)) {
1664                if ((parentIsSystem || itemIsSystem) && abortEditAboutSystem(femaleSocket.getParent())) return true;
1665                femaleSocket.doSocketOperation(oper);
1666                return true;
1667            }
1668        }
1669        return false;
1670    }
1671
1672    private boolean parentIsLocked(FemaleSocket femaleSocket) {
1673        Base parent = femaleSocket.getParent();
1674        while ((parent != null) && !(parent instanceof MaleSocket)) {
1675            parent = parent.getParent();
1676        }
1677        return (parent != null) && ((MaleSocket)parent).isLocked();
1678    }
1679
1680    protected final class PopupMenu extends JPopupMenu implements ActionListener {
1681
1682        private final JTree _tree;
1683//        private final FemaleSocketTreeModel _model;
1684        private final FemaleSocket _currentFemaleSocket;
1685        private final TreePath _currentPath;
1686
1687        private JMenuItem menuItemRenameSocket;
1688        private JMenuItem menuItemRemove;
1689        private JMenuItem menuItemCut;
1690        private JMenuItem menuItemCopy;
1691        private JMenuItem menuItemPaste;
1692        private JMenuItem menuItemPasteCopy;
1693        private final Map<FemaleSocketOperation, JMenuItem> menuItemFemaleSocketOperation
1694                = new HashMap<>();
1695        private JMenuItem menuItemEnable;
1696        private JMenuItem menuItemDisable;
1697        private JMenuItem menuItemLock;
1698        private JMenuItem menuItemUnlock;
1699        private JMenuItem menuItemLocalVariables;
1700        private JMenuItem menuItemChangeUsername;
1701        private JMenuItem menuItemExecuteEvaluate;
1702//        private JMenuItem menuItemExpandTree;
1703
1704        private final boolean _isConnected;
1705        private final boolean _canConnectFromClipboard;
1706        private final boolean _disableForRoot;
1707        private final boolean _isLocked;
1708        private final boolean _parentIsLocked;
1709
1710
1711        PopupMenu(int x, int y, FemaleSocket femaleSocket, TreePath path, boolean onlyAddItems) {
1712
1713            if (_treePane._tree == null) throw new IllegalArgumentException("_tree is null");
1714
1715            _tree = _treePane._tree;
1716
1717            _currentFemaleSocket = femaleSocket;
1718            _currentPath = path;
1719            _isConnected = femaleSocket.isConnected();
1720
1721            Clipboard clipboard = InstanceManager.getDefault(LogixNG_Manager.class).getClipboard();
1722
1723            MaleSocket topItem = clipboard.getTopItem();
1724
1725            _canConnectFromClipboard =
1726                    topItem != null
1727                    && femaleSocket.isCompatible(topItem)
1728                    && !femaleSocket.isAncestor(topItem);
1729
1730            _disableForRoot = _disableRootRemoveCutCopy
1731                    && (_currentFemaleSocket == _treePane._femaleRootSocket);
1732
1733            _isLocked = _isConnected && femaleSocket.getConnectedSocket().isLocked();
1734
1735            _parentIsLocked = parentIsLocked(femaleSocket);
1736
1737            if (onlyAddItems) {
1738                addNewItemTypes(this);
1739            } else {
1740                if (_disableRootPopup
1741                        && (_currentFemaleSocket == _treePane._femaleRootSocket)) {
1742                    JmriJOptionPane.showMessageDialog(null,
1743                            Bundle.getMessage("TreeEditor_RootHasNoPopupMenu"),
1744                            Bundle.getMessage("TreeEditor_Info"),
1745                            JmriJOptionPane.ERROR_MESSAGE);
1746                    return;
1747                }
1748
1749                menuItemRenameSocket = new JMenuItem(Bundle.getMessage("PopupMenuRenameSocket"));
1750                menuItemRenameSocket.addActionListener(this);
1751                menuItemRenameSocket.setActionCommand(ACTION_COMMAND_RENAME_SOCKET);
1752                add(menuItemRenameSocket);
1753                addSeparator();
1754
1755                if (!_isConnected && !_parentIsLocked) {
1756                    JMenu addMenu = new JMenu(Bundle.getMessage("PopupMenuAdd"));
1757//                    addMenu.setMnemonic(KeyEvent.VK_F);
1758//                    addMenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1759                    addNewItemTypes(addMenu);
1760                    add(addMenu);
1761                }
1762
1763                if (_isConnected && !_isLocked) {
1764                    JMenuItem menuItemEdit = new JMenuItem(Bundle.getMessage("PopupMenuEdit"));
1765                    menuItemEdit.addActionListener(this);
1766                    menuItemEdit.setActionCommand(ACTION_COMMAND_EDIT);
1767                    menuItemEdit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1768                    add(menuItemEdit);
1769                }
1770                addSeparator();
1771                menuItemRemove = new JMenuItem(Bundle.getMessage("PopupMenuRemove"));
1772                menuItemRemove.addActionListener(this);
1773                menuItemRemove.setActionCommand(ACTION_COMMAND_REMOVE);
1774                menuItemRemove.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1775                add(menuItemRemove);
1776                addSeparator();
1777                menuItemCut = new JMenuItem(Bundle.getMessage("PopupMenuCut"));
1778                menuItemCut.addActionListener(this);
1779                menuItemCut.setActionCommand(ACTION_COMMAND_CUT);
1780                menuItemCut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1781                add(menuItemCut);
1782                menuItemCopy = new JMenuItem(Bundle.getMessage("PopupMenuCopy"));
1783                menuItemCopy.addActionListener(this);
1784                menuItemCopy.setActionCommand(ACTION_COMMAND_COPY);
1785                menuItemCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1786                add(menuItemCopy);
1787                menuItemPaste = new JMenuItem(Bundle.getMessage("PopupMenuPaste"));
1788                menuItemPaste.addActionListener(this);
1789                menuItemPaste.setActionCommand(ACTION_COMMAND_PASTE);
1790                menuItemPaste.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1791                add(menuItemPaste);
1792                menuItemPasteCopy = new JMenuItem(Bundle.getMessage("PopupMenuPasteCopy"));
1793                menuItemPasteCopy.addActionListener(this);
1794                menuItemPasteCopy.setActionCommand(ACTION_COMMAND_PASTE_COPY);
1795                menuItemPasteCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V,
1796                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx() + InputEvent.SHIFT_DOWN_MASK));
1797                add(menuItemPasteCopy);
1798                addSeparator();
1799
1800                for (FemaleSocketOperation oper : FemaleSocketOperation.values()) {
1801                    JMenuItem menuItem = new JMenuItem(oper.toString());
1802                    menuItem.addActionListener(this);
1803                    menuItem.setActionCommand(oper.name());
1804                    add(menuItem);
1805                    menuItemFemaleSocketOperation.put(oper, menuItem);
1806                    if (oper.hasKey()) {
1807                        menuItem.setAccelerator(KeyStroke.getKeyStroke(
1808                                oper.getKeyCode(), oper.getModifiers()));
1809                    }
1810                }
1811
1812                addSeparator();
1813                menuItemEnable = new JMenuItem(Bundle.getMessage("PopupMenuEnable"));
1814                menuItemEnable.addActionListener(this);
1815                menuItemEnable.setActionCommand(ACTION_COMMAND_ENABLE);
1816                menuItemEnable.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D,
1817                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx() + InputEvent.SHIFT_DOWN_MASK));
1818                add(menuItemEnable);
1819                menuItemDisable = new JMenuItem(Bundle.getMessage("PopupMenuDisable"));
1820                menuItemDisable.addActionListener(this);
1821                menuItemDisable.setActionCommand(ACTION_COMMAND_DISABLE);
1822                menuItemDisable.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D,
1823                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
1824                add(menuItemDisable);
1825                menuItemLock = new JMenuItem(Bundle.getMessage("PopupMenuLock"));
1826                menuItemLock.addActionListener(this);
1827                menuItemLock.setActionCommand(ACTION_COMMAND_LOCK);
1828                add(menuItemLock);
1829                menuItemUnlock = new JMenuItem(Bundle.getMessage("PopupMenuUnlock"));
1830                menuItemUnlock.addActionListener(this);
1831                menuItemUnlock.setActionCommand(ACTION_COMMAND_UNLOCK);
1832                add(menuItemUnlock);
1833
1834                addSeparator();
1835                menuItemLocalVariables = new JMenuItem(Bundle.getMessage("PopupMenuLocalVariables"));
1836                menuItemLocalVariables.addActionListener(this);
1837                menuItemLocalVariables.setActionCommand(ACTION_COMMAND_LOCAL_VARIABLES);
1838                add(menuItemLocalVariables);
1839
1840                addSeparator();
1841                menuItemChangeUsername = new JMenuItem(Bundle.getMessage("PopupMenuChangeUsername"));
1842                menuItemChangeUsername.addActionListener(this);
1843                menuItemChangeUsername.setActionCommand(ACTION_COMMAND_CHANGE_USERNAME);
1844                add(menuItemChangeUsername);
1845
1846                if (_enableExecuteEvaluate) {
1847                    addSeparator();
1848                    menuItemExecuteEvaluate = new JMenuItem();  // The text is set later
1849                    menuItemExecuteEvaluate.addActionListener(this);
1850                    menuItemExecuteEvaluate.setActionCommand(ACTION_COMMAND_EXECUTE_EVALUATE);
1851                    add(menuItemExecuteEvaluate);
1852                }
1853    /*
1854                addSeparator();
1855                menuItemExpandTree = new JMenuItem(Bundle.getMessage("PopupMenuExpandTree"));
1856                menuItemExpandTree.addActionListener(this);
1857                menuItemExpandTree.setActionCommand(ACTION_COMMAND_EXPAND_TREE);
1858                add(menuItemExpandTree);
1859    */
1860                setOpaque(true);
1861                setLightWeightPopupEnabled(true);
1862
1863                menuItemRemove.setEnabled(_isConnected && !_isLocked && !_parentIsLocked && !_disableForRoot);
1864                menuItemCut.setEnabled(_isConnected && !_isLocked && !_parentIsLocked && !_disableForRoot);
1865                menuItemCopy.setEnabled(_isConnected && !_disableForRoot);
1866                menuItemPaste.setEnabled(!_isConnected && !_parentIsLocked && _canConnectFromClipboard);
1867                menuItemPasteCopy.setEnabled(!_isConnected && !_parentIsLocked && _canConnectFromClipboard);
1868
1869                if (_isConnected && !_disableForRoot) {
1870                    menuItemEnable.setEnabled(!femaleSocket.getConnectedSocket().isEnabled() && !_isLocked);
1871                    menuItemDisable.setEnabled(femaleSocket.getConnectedSocket().isEnabled() && !_isLocked);
1872                } else {
1873                    menuItemEnable.setEnabled(false);
1874                    menuItemDisable.setEnabled(false);
1875                }
1876
1877                for (FemaleSocketOperation oper : FemaleSocketOperation.values()) {
1878                    JMenuItem menuItem = menuItemFemaleSocketOperation.get(oper);
1879                    menuItem.setEnabled(femaleSocket.isSocketOperationAllowed(oper) && !_parentIsLocked);
1880                }
1881
1882                AtomicBoolean isAnyLocked = new AtomicBoolean(false);
1883                AtomicBoolean isAnyUnlocked = new AtomicBoolean(false);
1884
1885                _currentFemaleSocket.forEntireTree((item) -> {
1886                    if (item instanceof MaleSocket) {
1887                        isAnyLocked.set(isAnyLocked.get() || ((MaleSocket)item).isLocked());
1888                        isAnyUnlocked.set(isAnyUnlocked.get() || !((MaleSocket)item).isLocked());
1889                    }
1890                });
1891                menuItemLock.setEnabled(isAnyUnlocked.get());
1892                menuItemUnlock.setEnabled(isAnyLocked.get());
1893
1894                menuItemLocalVariables.setEnabled(
1895                        femaleSocket.isConnected()
1896                        && femaleSocket.getConnectedSocket().isSupportingLocalVariables()
1897                        && !_isLocked);
1898
1899                menuItemChangeUsername.setEnabled(femaleSocket.isConnected() && !_isLocked);
1900
1901                if (_enableExecuteEvaluate) {
1902                    menuItemExecuteEvaluate.setEnabled(femaleSocket.isConnected());
1903
1904                    if (femaleSocket.isConnected()) {
1905                        Base object = _currentFemaleSocket.getConnectedSocket();
1906                        if (object == null) throw new NullPointerException("object is null");
1907                        while (object instanceof MaleSocket) {
1908                            object = ((MaleSocket)object).getObject();
1909                        }
1910                        menuItemExecuteEvaluate.setText(
1911                                SwingTools.getSwingConfiguratorForClass(object.getClass())
1912                                        .getExecuteEvaluateMenuText());
1913                    }
1914                }
1915            }
1916
1917            show(_tree, x, y);
1918        }
1919
1920        private void addNewItemTypes(Container container) {
1921            Map<Category, List<Class<? extends Base>>> connectableClasses =
1922                    _currentFemaleSocket.getConnectableClasses();
1923            List<Category> list = new ArrayList<>(connectableClasses.keySet());
1924            Collections.sort(list);
1925            for (Category category : list) {
1926                List<SwingConfiguratorInterface> sciList = new ArrayList<>();
1927                List<Class<? extends Base>> classes = connectableClasses.get(category);
1928                if (classes != null && !classes.isEmpty()) {
1929                    for (Class<? extends Base> clazz : classes) {
1930                        SwingConfiguratorInterface sci = SwingTools.getSwingConfiguratorForClass(clazz);
1931                        if (sci != null) {
1932                            sciList.add(sci);
1933                        } else {
1934                            log.error("Class {} has no swing configurator interface", clazz.getName());
1935                        }
1936                    }
1937                }
1938
1939                Collections.sort(sciList);
1940
1941                JMenu categoryMenu = new JMenu(category.toString());
1942                for (SwingConfiguratorInterface sci : sciList) {
1943                    JMenuItem item = new JMenuItem(sci.toString());
1944                    item.addActionListener((e) -> {
1945                        createAddFrame(_currentFemaleSocket, _currentPath, sci);
1946                    });
1947                    categoryMenu.add(item);
1948                }
1949                container.add(categoryMenu);
1950            }
1951        }
1952
1953        @Override
1954        public void actionPerformed(ActionEvent e) {
1955            doIt(e.getActionCommand(), _currentFemaleSocket, _currentPath);
1956        }
1957
1958    }
1959
1960
1961    // This class is copied from BeanTableDataModel
1962    private class DeleteBeanWorker extends SwingWorker<Void, Void> {
1963
1964        private final FemaleSocket _currentFemaleSocket;
1965        private final TreePath _currentPath;
1966        MaleSocket _maleSocket;
1967
1968        public DeleteBeanWorker(FemaleSocket currentFemaleSocket, TreePath currentPath) {
1969            _currentFemaleSocket = currentFemaleSocket;
1970            _currentPath = currentPath;
1971            _maleSocket = _currentFemaleSocket.getConnectedSocket();
1972        }
1973
1974        public int getDisplayDeleteMsg() {
1975            return InstanceManager.getDefault(UserPreferencesManager.class).getMultipleChoiceOption(getClassName(), "deleteInUse");
1976        }
1977
1978        public void setDisplayDeleteMsg(int boo) {
1979            InstanceManager.getDefault(UserPreferencesManager.class).setMultipleChoiceOption(getClassName(), "deleteInUse", boo);
1980        }
1981
1982        public void doDelete() {
1983            try {
1984                _currentFemaleSocket.disconnect();
1985
1986                _maleSocket.getManager().deleteBean(_maleSocket, "DoDelete");
1987            } catch (PropertyVetoException e) {
1988                //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto
1989                log.error("Unexpected doDelete failure for {}, {}", _maleSocket, e.getMessage() );
1990            }
1991        }
1992
1993        /**
1994         * {@inheritDoc}
1995         */
1996        @Override
1997        public Void doInBackground() {
1998            _treePane._femaleRootSocket.unregisterListeners();
1999
2000            StringBuilder message = new StringBuilder();
2001            try {
2002                _maleSocket.getManager().deleteBean(_maleSocket, "CanDelete");  // NOI18N
2003            } catch (PropertyVetoException e) {
2004                if (e.getPropertyChangeEvent().getPropertyName().equals("DoNotDelete")) { // NOI18N
2005                    log.warn("Do not Delete {}, {}", _maleSocket, e.getMessage());
2006                    message.append(Bundle.getMessage(
2007                            "VetoDeleteBean",
2008                            ((NamedBean)_maleSocket.getObject()).getBeanType(),
2009                            ((NamedBean)_maleSocket.getObject()).getDisplayName(
2010                                    NamedBean.DisplayOptions.USERNAME_SYSTEMNAME),
2011                            e.getMessage()));
2012                    JmriJOptionPane.showMessageDialog(null, message.toString(),
2013                            Bundle.getMessage("WarningTitle"),
2014                            JmriJOptionPane.ERROR_MESSAGE);
2015                    return null;
2016                }
2017                message.append(e.getMessage());
2018            }
2019            List<String> listenerRefs = new ArrayList<>();
2020            _maleSocket.getListenerRefsIncludingChildren(listenerRefs);
2021            int count = listenerRefs.size();
2022            log.debug("Delete with {}", count);
2023            if (getDisplayDeleteMsg() == 0x02 && message.toString().isEmpty()) {
2024                doDelete();
2025            } else {
2026                final JDialog dialog = new JDialog();
2027                dialog.setTitle(Bundle.getMessage("WarningTitle"));
2028                dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
2029                JPanel container = new JPanel();
2030                container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
2031                container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
2032                if (count > 0) { // warn of listeners attached before delete
2033
2034                    String prompt = _maleSocket.getChildCount() > 0 ? "DeleteWithChildrenPrompt" : "DeletePrompt";
2035                    JLabel question = new JLabel(Bundle.getMessage(
2036                            prompt,
2037                            ((NamedBean)_maleSocket.getObject())
2038                                    .getDisplayName(NamedBean.DisplayOptions.USERNAME_SYSTEMNAME)));
2039                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
2040                    container.add(question);
2041
2042                    ArrayList<String> tempListenerRefs = new ArrayList<>();
2043
2044                    tempListenerRefs.addAll(listenerRefs);
2045
2046                    if (tempListenerRefs.size() > 0) {
2047                        ArrayList<String> listeners = new ArrayList<>();
2048                        for (int i = 0; i < tempListenerRefs.size(); i++) {
2049                            if (!listeners.contains(tempListenerRefs.get(i))) {
2050                                listeners.add(tempListenerRefs.get(i));
2051                            }
2052                        }
2053
2054                        message.append("<br>");
2055                        message.append(Bundle.getMessage("ReminderInUse", count));
2056                        message.append("<ul>");
2057                        for (int i = 0; i < listeners.size(); i++) {
2058                            message.append("<li>");
2059                            message.append(listeners.get(i));
2060                            message.append("</li>");
2061                        }
2062                        message.append("</ul>");
2063
2064                        JEditorPane pane = new JEditorPane();
2065                        pane.setContentType("text/html");
2066                        pane.setText("<html>" + message.toString() + "</html>");
2067                        pane.setEditable(false);
2068                        JScrollPane jScrollPane = new JScrollPane(pane);
2069                        container.add(jScrollPane);
2070                    }
2071                } else {
2072                    String prompt = _maleSocket.getChildCount() > 0 ? "DeleteWithChildrenPrompt" : "DeletePrompt";
2073                    String msg = MessageFormat.format(Bundle.getMessage(prompt),
2074                            new Object[]{_maleSocket.getSystemName()});
2075                    JLabel question = new JLabel(msg);
2076                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
2077                    container.add(question);
2078                }
2079
2080                final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
2081                remember.setFont(remember.getFont().deriveFont(10f));
2082                remember.setAlignmentX(Component.CENTER_ALIGNMENT);
2083
2084                JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
2085                JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
2086                JPanel button = new JPanel();
2087                button.setAlignmentX(Component.CENTER_ALIGNMENT);
2088                button.add(yesButton);
2089                button.add(noButton);
2090                container.add(button);
2091
2092                noButton.addActionListener((ActionEvent e) -> {
2093                    //there is no point in remembering this the user will never be
2094                    //able to delete a bean!
2095                    dialog.dispose();
2096                });
2097
2098                yesButton.addActionListener((ActionEvent e) -> {
2099                    if (remember.isSelected()) {
2100                        setDisplayDeleteMsg(0x02);
2101                    }
2102                    doDelete();
2103                    dialog.dispose();
2104                });
2105                container.add(remember);
2106                container.setAlignmentX(Component.CENTER_ALIGNMENT);
2107                container.setAlignmentY(Component.CENTER_ALIGNMENT);
2108                dialog.getContentPane().add(container);
2109                dialog.pack();
2110                dialog.setLocation(
2111                        (Toolkit.getDefaultToolkit().getScreenSize().width) / 2 - dialog.getWidth() / 2,
2112                        (Toolkit.getDefaultToolkit().getScreenSize().height) / 2 - dialog.getHeight() / 2);
2113                dialog.setModal(true);
2114                dialog.setVisible(true);
2115            }
2116            if (_treePane._femaleRootSocket.isActive()) {
2117                _treePane._femaleRootSocket.registerListeners();
2118            }
2119            return null;
2120        }
2121
2122        /**
2123         * {@inheritDoc} Minimal implementation to catch and log errors
2124         */
2125        @Override
2126        protected void done() {
2127            try {
2128                get();  // called to get errors
2129            } catch (InterruptedException | java.util.concurrent.ExecutionException e) {
2130                log.error("Exception while deleting bean", e);
2131            }
2132            _treePane.updateTree(_currentFemaleSocket, _currentPath.getPath());
2133        }
2134    }
2135
2136
2137
2138    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TreeEditor.class);
2139
2140}