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