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