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